diff --git a/.classpath b/.classpath index 23936f7..638e532 100644 --- a/.classpath +++ b/.classpath @@ -8,6 +8,6 @@ - + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c3e7b8f --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +## class files +classes/ + +## auto-generate code from javacc/jjtree +src/bsh/Parser.java +src/bsh/ParserConstants.java +src/bsh/ParserTokenManager.java +src/bsh/ParserTreeConstants.java +src/bsh/bsh.jj +/bin/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..bdc988a --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +This is the fork of [beanshell.org](http://www.beanshell.org/) called Beanshell2 which lived between 2011 and +2014 at [code.google.com](https://code.google.com/p/beanshell2). The motivation of the fork was that the +original author was no longer maintaining it. + +In 2015 the repository moved here when Google retired their code hosting site. + +The old version is again maintained at [github.com/beanshell](https://github.com/beanshell). There is some +ongoing effort to integrate the changes and improvements made to the project there. + +The development branch here is [v2.1](https://github.com/pejobo/beanshell2/tree/v2.1), the latest 'released' +version is +[2.1.9](https://github.com/pejobo/beanshell2/raw/v2.1/dist/bsh-2.1.9.jar) +which maps to commit +[a70056fbe9727d8eadf8e21f3089cbe9f4f0913e](https://github.com/pejobo/beanshell2/commit/a70056fbe9727d8eadf8e21f3089cbe9f4f0913e). + +See [releases page](https://github.com/pejobo/beanshell2/dist/README.md) for more information. + + diff --git a/build.xml b/build.xml index 9dc2381..b7d31e3 100644 --- a/build.xml +++ b/build.xml @@ -18,7 +18,7 @@ - + diff --git a/dist/README.md b/dist/README.md new file mode 100644 index 0000000..0312a48 --- /dev/null +++ b/dist/README.md @@ -0,0 +1,63 @@ +March 13, 2018 + +* Update [2.1.9](https://github.com/pejobo/beanshell2/raw/v2.1/dist/bsh-2.1.9.jar) + * Drop of all remote code execution capabilities + * Restricting (de)serialization for security reasons (the serialization feature may be dropped in one of the next + releases) [#109](https://github.com/pejobo/beanshell2/issues/109) + * Ability to run with Java-9 and Java-10 with restricted module access (jvm flag _--illegal-access=deny_). + * Fixed a deadlock when pasting code into the graphical console + * [SHA512](https://raw.githubusercontent.com/pejobo/beanshell2/v2.1/dist/bsh-2.1.9.jar.sha512sum) + +Feb. 20, 2014 + +* Update [2.1.8](https://github.com/pejobo/beanshell2/raw/5b925f056c7a4b192fcd7389c9362d4f43403f70/downloads/bsh-2.1.8.jar) + * fixes [#97](https://github.com/pejobo/beanshell2/issues/97), + [#98](https://github.com/pejobo/beanshell2/issues/98), and + [#99](https://github.com/pejobo/beanshell2/issues/99) - all variants of a regression introduced with + [#88](https://github.com/pejobo/beanshell2/issues/88) + + +Nov. 6, 2013 + +* Update 2.1.7 + * Another hotfix when running beanshell in a security restricted environment + * Fix for finally block not executed when an exception is thrown in catch block - thanks to Lorenzo Cameroni for pointing this out and suggesting a fix + + +Sep. 27, 2013 + +* Update v2.1.6 + * This release mainly fixes issues of running beanshell in a security restricted environment. This may break existing + scriptes which define classes with protected methods, constructors or fields or which access inherited protected + methods, constructors or fields. If your (script) code doesn't explicitly switch on the accessibility mode your + script code will break with this update. + To receive the old behaviour either call `bsh.Capabilities.setAccessibility(true)` in your java code or + `setAccessibility(true)` in your script code. This change was done to allow the usage of beanshell2 in security + restricted environments. See issue [#88](https://github.com/pejobo/beanshell2/issues/88) for code changes. + * New version number scheme (drop of b for build). + + +Nov. 21, 2011 + +* Update v2.1b5, fixing + * Do-while loop does not check condition on "continue" - issue [#57](https://github.com/pejobo/beanshell2/issues/57) + * Fixes when using JSR-223 an exception which is thrown for clause that shouldn't be evaluated - issue + [#60](https://github.com/pejobo/beanshell2/issues/60). + +Older downloads are still available at [code.google.com](https://code.google.com/archive/p/beanshell2/downloads) + +Other notable changes not mentioned above in contrast to the latest version available at +[beanshell.org](http://www.beanshell.org) are: +* The support for parsing of java files through the class loader has been dropped. It was considered more harmful than + helpful. +* The support of Java-5 varargs. +* Support for long string literals: + ``` + xml = """ + + Beanshell2 + + """" + ``` +* Build-in [jsr-233](https://www.jcp.org/en/jsr/detail?id=223) support (_Scripting for the Java Platform_). + \ No newline at end of file diff --git a/src/README.txt b/src/README.txt deleted file mode 100644 index 6ccb9c8..0000000 --- a/src/README.txt +++ /dev/null @@ -1,7 +0,0 @@ --- This is BeanShell2 -- - -BeanShell2 is a fork of http://www.beanshell.org/ - -For update and documentation see http://code.google.com/p/beanshell2 - -BeanShell2 is designed to work with versions of Java 1.5 and later diff --git a/src/bsh/BSHLiteral.java b/src/bsh/BSHLiteral.java index 06a60f3..b1d9743 100644 --- a/src/bsh/BSHLiteral.java +++ b/src/bsh/BSHLiteral.java @@ -85,8 +85,36 @@ private char getEscapeChar(char ch) return ch; } + public static String decode(String str) { + StringBuilder sb = new StringBuilder(str.length()); + char[] chars = str.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (i + 1 < chars.length && c == '\\' && chars[i + 1] == 'u') { + char cc = 0; + for (int j = 0; j < 4; j++) { + char ch = Character.toLowerCase(chars[i + 2 + j]); + if ('0' <= ch && ch <= '9' || 'a' <= ch && ch <= 'f' || 'A' <= ch && ch <= 'F') { + cc |= (Character.digit(ch, 16) << (3 - j) * 4); + } else { + cc = 0; + break; + } + } + if (cc > 0) { + i += 5; + sb.append(cc); + continue; + } + } + sb.append(c); + } + return sb.toString(); + } + public void charSetup(String str) { + str = decode(str); char ch = str.charAt(0); if(ch == '\\') { @@ -104,6 +132,7 @@ public void charSetup(String str) void stringSetup(String str) { + str = decode(str); StringBuilder buffer = new StringBuilder(); int len = str.length(); for(int i = 0; i < len; i++) diff --git a/src/bsh/BSHWhileStatement.java b/src/bsh/BSHWhileStatement.java index 791f7b8..81a9cd8 100644 --- a/src/bsh/BSHWhileStatement.java +++ b/src/bsh/BSHWhileStatement.java @@ -31,68 +31,64 @@ * * *****************************************************************************/ - package bsh; /** - This class handles both while(){} statements and do{}while() statements. + * This class handles both {@code while} statements and {@code do..while} statements. */ -class BSHWhileStatement extends SimpleNode implements ParserConstants -{ - public boolean isDoStatement; +class BSHWhileStatement extends SimpleNode implements ParserConstants { + + /** + * Set by Parser, default {@code false} + */ + boolean isDoStatement; - BSHWhileStatement(int id) { super(id); } + BSHWhileStatement(int id) { + super(id); + } - public Object eval( CallStack callstack, Interpreter interpreter) - throws EvalError - { + + public Object eval( CallStack callstack, Interpreter interpreter) throws EvalError { int numChild = jjtGetNumChildren(); // Order of body and condition is swapped for do / while - SimpleNode condExp, body = null; + final SimpleNode condExp; + final SimpleNode body; if ( isDoStatement ) { - condExp = (SimpleNode)jjtGetChild(1); - body =(SimpleNode)jjtGetChild(0); + condExp = (SimpleNode) jjtGetChild(1); + body = (SimpleNode) jjtGetChild(0); } else { - condExp = (SimpleNode)jjtGetChild(0); - if ( numChild > 1 ) // has body, else just for side effects - body =(SimpleNode)jjtGetChild(1); + condExp = (SimpleNode) jjtGetChild(0); + if ( numChild > 1 ) { + body = (SimpleNode) jjtGetChild(1); + } else { + body = null; + } } boolean doOnceFlag = isDoStatement; - while( - doOnceFlag || - BSHIfStatement.evaluateCondition(condExp, callstack, interpreter ) - ) - { - if ( body == null ) // no body? - continue; + while (doOnceFlag || BSHIfStatement.evaluateCondition(condExp, callstack, interpreter)) { + doOnceFlag = false; + // no body? + if ( body == null ) { + continue; + } Object ret = body.eval(callstack, interpreter); - - boolean breakout = false; - if(ret instanceof ReturnControl) - { - switch(((ReturnControl)ret).kind ) - { + if (ret instanceof ReturnControl) { + switch(( (ReturnControl)ret).kind ) { case RETURN: return ret; case CONTINUE: - continue; + break; case BREAK: - breakout = true; - break; + return Primitive.VOID; } } - if(breakout) - break; - - doOnceFlag = false; } - return Primitive.VOID; } diff --git a/src/bsh/BshClassManager.java b/src/bsh/BshClassManager.java index 253146b..079bc8d 100644 --- a/src/bsh/BshClassManager.java +++ b/src/bsh/BshClassManager.java @@ -35,6 +35,7 @@ import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.PrintWriter; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -185,8 +186,35 @@ public Class classForName( String name ) clas = plainClassForName( name ); } catch ( ClassNotFoundException e ) { /*ignore*/ } + // try scripted class + if ( clas == null && declaringInterpreter.getCompatibility() ) + clas = loadSourceClass( name ); + return clas; } + + // Move me to classpath/ClassManagerImpl??? + protected Class loadSourceClass( final String name ) { + final String fileName = '/' + name.replace('.', '/') + ".java"; + final InputStream in = getResourceAsStream( fileName ); + if ( in == null ) { + return null; + } + try { + Interpreter.debug("Loading class from source file: " + fileName); + declaringInterpreter.eval( new InputStreamReader(in) ); + } catch ( EvalError e ) { + if (Interpreter.DEBUG) { + e.printStackTrace(); + } + } + try { + return plainClassForName( name ); + } catch ( final ClassNotFoundException e ) { + Interpreter.debug("Class not found in source file: " + name); + return null; + } + } /** Perform a plain Class.forName() or call the externally provided diff --git a/src/bsh/BshScriptEngineFactory.java b/src/bsh/BshScriptEngineFactory.java index 90cab3b..6ac06ae 100644 --- a/src/bsh/BshScriptEngineFactory.java +++ b/src/bsh/BshScriptEngineFactory.java @@ -8,11 +8,11 @@ public class BshScriptEngineFactory implements javax.script.ScriptEngineFactory { // Begin impl ScriptEnginInfo - final List extensions = Arrays.asList("bsh", "java"); + final List extensions = Arrays.asList("bsh"); - final List mimeTypes = Arrays.asList("application/x-beanshell", "application/x-bsh", "application/x-java-source"); + final List mimeTypes = Arrays.asList("application/x-beanshell", "application/x-bsh"); - final List names = Arrays.asList("beanshell", "bsh", "java"); + final List names = Arrays.asList("beanshell", "bsh"); public String getEngineName() { diff --git a/src/bsh/Capabilities.java b/src/bsh/Capabilities.java index 9373720..a4b250a 100644 --- a/src/bsh/Capabilities.java +++ b/src/bsh/Capabilities.java @@ -33,6 +33,7 @@ package bsh; +import java.lang.reflect.Field; import java.util.Hashtable; /** @@ -80,6 +81,13 @@ public static void setAccessibility( boolean b ) // test basic access try { String.class.getDeclaredMethods(); + try { + final Field field = Capabilities.class.getField("classes"); + field.setAccessible(true); + field.setAccessible(false); + } catch (NoSuchFieldException e) { + // ignore + } } catch ( SecurityException e ) { throw new Unavailable("Accessibility unavailable: "+e); } diff --git a/src/bsh/ClassGeneratorUtil.java b/src/bsh/ClassGeneratorUtil.java index 22adfe0..4d93e58 100644 --- a/src/bsh/ClassGeneratorUtil.java +++ b/src/bsh/ClassGeneratorUtil.java @@ -1000,7 +1000,7 @@ public static void initInstance(GeneratedClass instance, String className, Objec if (e instanceof InvocationTargetException) { e = (Exception) ((InvocationTargetException) e).getTargetException(); } - throw new InterpreterError("Error in class initialization: " + e); + throw new InterpreterError("Error in class initialization.", e); } } diff --git a/src/bsh/Interpreter.java b/src/bsh/Interpreter.java index 5698744..93ce086 100644 --- a/src/bsh/Interpreter.java +++ b/src/bsh/Interpreter.java @@ -107,7 +107,7 @@ public class Interpreter { /* --- Begin static members --- */ - public static final String VERSION = "2.1b5"; + public static final String VERSION = "2.2.0"; /* Debug utils are static so that they are reachable by code that doesn't necessarily have an interpreter reference (e.g. tracing in utils). @@ -117,6 +117,7 @@ necessarily have an interpreter reference (e.g. tracing in utils). turns it on or off. */ public static boolean DEBUG, TRACE, LOCALSCOPING; + public static boolean COMPATIBIILTY; // This should be per instance transient static PrintStream debug; @@ -160,6 +161,13 @@ necessarily have an interpreter reference (e.g. tracing in utils). /** Control the verbose printing of results for the show() command. */ private boolean showResults; + /** + * Compatibility mode. When {@code true} missing classes are tried to create from corresponding java source files. + * Default value is {@code false}, could be changed to {@code true} by setting the system property + * "bsh.compatibility" to "true". + */ + private boolean compatibility = COMPATIBIILTY; + /* --- End instance data --- */ /** @@ -1121,17 +1129,13 @@ public boolean getStrictJava() { static void staticInit() { - /* - Apparently in some environments you can't catch the security exception - at all... e.g. as an applet in IE ... will probably have to work - around - */ try { systemLineSeparator = System.getProperty("line.separator"); debug = System.err; DEBUG = Boolean.getBoolean("debug"); TRACE = Boolean.getBoolean("trace"); LOCALSCOPING = Boolean.getBoolean("localscoping"); + COMPATIBIILTY = Boolean.getBoolean("bsh.compatibility"); String outfilename = System.getProperty("outfile"); if ( outfilename != null ) redirectOutputToFile( outfilename ); @@ -1255,4 +1259,27 @@ public static void setShutdownOnExit(final boolean value) { } } + + /** + * Compatibility mode. When {@code true} missing classes are tried to create from corresponding java source files. + * The Default value is {@code false}. This could be changed to {@code true} by setting the system property + * "bsh.compatibility" to "true". + * + * @see #setCompatibility(boolean) + */ + public boolean getCompatibility() { + return compatibility; + } + + + /** + * Setting compatibility mode. When {@code true} missing classes are tried to create from corresponding java source + * files. The Default value is {@code false}. This could be changed to {@code true} by setting the system property + * "bsh.compatibility" to "true". + * + * @see #getCompatibility() + */ + public void setCompatibility(final boolean value) { + compatibility = value; + } } \ No newline at end of file diff --git a/src/bsh/JavaCharStream.java b/src/bsh/JavaCharStream.java index 92064ea..f398b08 100644 --- a/src/bsh/JavaCharStream.java +++ b/src/bsh/JavaCharStream.java @@ -260,80 +260,9 @@ public char readChar() throws java.io.IOException if (++bufpos == available) AdjustBuffSize(); - - if ((buffer[bufpos] = c = ReadByte()) == '\\') - { - UpdateLineColumn(c); - - int backSlashCnt = 1; - - for (;;) // Read all the backslashes - { - if (++bufpos == available) - AdjustBuffSize(); - - try - { - if ((buffer[bufpos] = c = ReadByte()) != '\\') - { - UpdateLineColumn(c); - // found a non-backslash char. - if ((c == 'u') && ((backSlashCnt & 1) == 1)) - { - if (--bufpos < 0) - bufpos = bufsize - 1; - - break; - } - - backup(backSlashCnt); - return '\\'; - } - } - catch(java.io.IOException e) - { - if (backSlashCnt > 1) - backup(backSlashCnt); - - return '\\'; - } - - UpdateLineColumn(c); - backSlashCnt++; - } - - // Here, we have seen an odd number of backslash's followed by a 'u' - try - { - while ((c = ReadByte()) == 'u') - ++column; - - buffer[bufpos] = c = (char)(hexval(c) << 12 | - hexval(ReadByte()) << 8 | - hexval(ReadByte()) << 4 | - hexval(ReadByte())); - - column += 4; - } - catch(java.io.IOException e) - { - throw new Error("Invalid escape character at line " + line + - " column " + column + "."); - } - - if (backSlashCnt == 1) - return c; - else - { - backup(backSlashCnt - 1); - return '\\'; - } - } - else - { - UpdateLineColumn(c); - return (c); - } + buffer[bufpos] = c = ReadByte(); + UpdateLineColumn(c); + return c; } /** diff --git a/src/bsh/LHS.java b/src/bsh/LHS.java index 1db674f..059c534 100644 --- a/src/bsh/LHS.java +++ b/src/bsh/LHS.java @@ -205,7 +205,7 @@ public Object assign( Object val, boolean strictJava ) ((Primitive)val).getValue() : val; // This should probably be in Reflect.java - field.setAccessible(true); + Reflect.setAccessible(field); field.set( object, fieldVal ); return val; } diff --git a/src/bsh/Name.java b/src/bsh/Name.java index dbb50a9..22ce562 100644 --- a/src/bsh/Name.java +++ b/src/bsh/Name.java @@ -842,7 +842,8 @@ public Object invokeMethod( if (obj == Primitive.NULL) throw new UtilTargetError( new NullPointerException( - "Null Pointer in Method Invocation" ) ); + "Null Pointer in Method Invocation of " +methodName + +"() on variable: "+targetName) ); // some other primitive // should avoid calling methods on primitive, as we do diff --git a/src/bsh/NameSpace.java b/src/bsh/NameSpace.java index 16bdef6..1ec8504 100644 --- a/src/bsh/NameSpace.java +++ b/src/bsh/NameSpace.java @@ -1216,7 +1216,8 @@ private Class classForName( String name ) protected void getAllNamesAux( List list ) { list.addAll( variables.keySet() ); - list.addAll( methods.keySet() ); + if ( methods != null ) + list.addAll( methods.keySet() ); if ( parent != null ) parent.getAllNamesAux( list ); } diff --git a/src/bsh/ParseException.java b/src/bsh/ParseException.java index 6dba65a..66a755c 100644 --- a/src/bsh/ParseException.java +++ b/src/bsh/ParseException.java @@ -106,7 +106,7 @@ public ParseException() { public ParseException(String message) { // Begin BeanShell Modification - super constructor args // null node, null callstack, ParseException knows where the error is. - super( message, null, null ); + super(message, null, null); // End BeanShell Modification - super constructor args specialConstructor = false; } @@ -123,6 +123,7 @@ public ParseException(String message,Throwable cause) { * This variable determines which constructor was used to create * this object and thereby affects the semantics of the * "getMessage" method (see below). + * This is the same as "currentToken != null". */ protected boolean specialConstructor; @@ -273,7 +274,25 @@ protected String add_escapes(String str) { public int getErrorLineNumber() { - return currentToken.next.beginLine; + if (currentToken == null) { + String message = getMessage(); + int index = message.indexOf(" at line "); + if (index > -1) { + message = message.substring(index + 9); + index = message.indexOf(','); + try { + if (index == -1) { + return Integer.parseInt(message); + } + return Integer.parseInt(message.substring(0, index)); + } catch (NumberFormatException e) { + // ignore, we have no valid line information, just return -1 for now + } + } + return -1; + } else { + return currentToken.next.beginLine; + } } public String getErrorText() { diff --git a/src/bsh/PreparsedScript.java b/src/bsh/PreparsedScript.java index 5244bdf..b06d7ee 100644 --- a/src/bsh/PreparsedScript.java +++ b/src/bsh/PreparsedScript.java @@ -57,7 +57,8 @@ public Object invoke(final Map context) throws EvalError { final BshMethod method = new BshMethod(_method.getName(), _method.getReturnType(), _method.getParameterNames(), _method.getParameterTypes(), _method.methodBody, nameSpace, _method.getModifiers()); for (final Map.Entry entry : context.entrySet()) { try { - nameSpace.setVariable(entry.getKey(), entry.getValue(), false); + final Object value = entry.getValue(); + nameSpace.setVariable(entry.getKey(), value != null ? value : Primitive.NULL, false); } catch (final UtilEvalError e) { throw new EvalError("cannot set variable '" + entry.getKey() + '\'', null, null, e); } diff --git a/src/bsh/Primitive.java b/src/bsh/Primitive.java index 965965b..20a52e1 100644 --- a/src/bsh/Primitive.java +++ b/src/bsh/Primitive.java @@ -756,6 +756,10 @@ static float floatUnaryOperation(Float F, int kind) return operand; case MINUS: return -operand; + case INCR: + return operand + 1; + case DECR: + return operand - 1; default: throw new InterpreterError("bad float unaryOperation"); } @@ -771,6 +775,10 @@ static double doubleUnaryOperation(Double D, int kind) return operand; case MINUS: return -operand; + case INCR: + return operand + 1; + case DECR: + return operand - 1; default: throw new InterpreterError("bad double unaryOperation"); } diff --git a/src/bsh/Reflect.java b/src/bsh/Reflect.java index 9421024..301b626 100644 --- a/src/bsh/Reflect.java +++ b/src/bsh/Reflect.java @@ -37,6 +37,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.List; @@ -332,30 +333,27 @@ This method should be rewritten to use getFields() and avoid catching */ private static Field findAccessibleField(Class clas, String fieldName) throws UtilEvalError, NoSuchFieldException { Field field; - // Quick check catches public fields include those in interfaces try { field = clas.getField(fieldName); - field.setAccessible(true); return field; } catch (NoSuchFieldException e) { - // fallthrough - } - - // Now, on with the hunt... - while (clas != null) { - try { - field = clas.getDeclaredField(fieldName); - field.setAccessible(true); - return field; - - // Not found, fall through to next class - - } catch (NoSuchFieldException e) { - // fallthrough + // ignore + } + if (Capabilities.haveAccessibility()) { + // try hidden fields (protected, private, package protected) + while (clas != null) { + try { + field = clas.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } catch (SecurityException e) { + break; + } catch (NoSuchFieldException e) { + // Not found, fall through to next class + } + clas = clas.getSuperclass(); } - - clas = clas.getSuperclass(); } throw new NoSuchFieldException(fieldName); } @@ -435,8 +433,14 @@ protected static Method resolveJavaMethod(BshClassManager bcm, Class clas, Strin // This is the first time we've seen this method, set accessibility // Note: even if it's a public method, we may have found it in a // non-public class - if (method != null && (!publicOnly || isPublic(method))) { - method.setAccessible(true); + if (method != null) { + if (!publicOnly || (isPublic(method) && !isPublic(method.getDeclaringClass()))) { + try { + method.setAccessible(true); + } catch (SecurityException e) { + method = null; + } + } } // If succeeded cache the resolved method. @@ -565,7 +569,7 @@ static Object constructObject(Class clas, Object[] args) throws ReflectError, In throw cantFindConstructor(clas, types); } - if (!isPublic(con)) { + if (!isPublic(con) && Capabilities.haveAccessibility()) { con.setAccessible(true); } @@ -882,23 +886,26 @@ private static ReflectError cantFindConstructor(Class clas, Class[] types) { } - private static boolean isPublic(Class c) { - return Modifier.isPublic(c.getModifiers()); + private static boolean isPublic(Member member) { + return Modifier.isPublic(member.getModifiers()); } - private static boolean isPublic(Method m) { - return Modifier.isPublic(m.getModifiers()); + private static boolean isPublic(Class clazz) { + return Modifier.isPublic(clazz.getModifiers()); } - private static boolean isPublic(Constructor c) { - return Modifier.isPublic(c.getModifiers()); + private static boolean isStatic(Method m) { + return Modifier.isStatic(m.getModifiers()); } - private static boolean isStatic(Method m) { - return Modifier.isStatic(m.getModifiers()); + static void setAccessible(final Field field) { + if ( ! isPublic(field) && Capabilities.haveAccessibility()) { + field.setAccessible(true); + } } + } diff --git a/src/bsh/Types.java b/src/bsh/Types.java index fcce754..c4903fb 100644 --- a/src/bsh/Types.java +++ b/src/bsh/Types.java @@ -170,7 +170,7 @@ Is the assignment legal via original Java (up to version 1.4) assignment rules, not including auto-boxing/unboxing. @param rhsType may be null to indicate primitive null value */ - static boolean isJavaBaseAssignable( Class lhsType, Class rhsType ) + static boolean isJavaBaseAssignable( Class lhsType, Class rhsType ) { /* Assignment to loose type, defer to bsh extensions @@ -373,7 +373,7 @@ If fromValue is (or would be) Primitive.NULL then fromType should be null. conversions... Where does that need to go? */ private static Object castObject( - Class toType, Class fromType, Object fromValue, + Class toType, Class fromType, Object fromValue, int operation, boolean checkOnly ) throws UtilEvalError { diff --git a/src/bsh/bsh.jjt b/src/bsh/bsh.jjt index 036c5fb..54e77cd 100644 --- a/src/bsh/bsh.jjt +++ b/src/bsh/bsh.jjt @@ -360,6 +360,7 @@ TOKEN : /* LITERALS */ ( ["n","t","b","r","f","\\","'","\""] | ["0"-"7"] ( ["0"-"7"] )? | ["0"-"3"] ["0"-"7"] ["0"-"7"] + | ["u"] (["0"-"9","a"-"f","A"-"F"])+ ) ) ) @@ -373,6 +374,7 @@ TOKEN : /* LITERALS */ ( ["n","t","b","r","f","\\","'","\""] | ["0"-"7"] ( ["0"-"7"] )? | ["0"-"3"] ["0"-"7"] ["0"-"7"] + | ["u"] (["0"-"9","a"-"f","A"-"F"])+ ) ) )* diff --git a/src/bsh/classpath/ClassManagerImpl.java b/src/bsh/classpath/ClassManagerImpl.java index bef34ef..1d278b4 100644 --- a/src/bsh/classpath/ClassManagerImpl.java +++ b/src/bsh/classpath/ClassManagerImpl.java @@ -245,6 +245,10 @@ public Class classForName( String name ) */ } + // Try scripted class + if ( c == null ) + c = loadSourceClass( name ); + // Cache result (or null for not found) cacheClassInfo( name, c ); diff --git a/src/bsh/commands/print.bsh b/src/bsh/commands/print.bsh index 846e366..15bee36 100644 --- a/src/bsh/commands/print.bsh +++ b/src/bsh/commands/print.bsh @@ -32,7 +32,7 @@ void print( arg ) print("}"); } else - this.interpreter.println(String.valueOf(arg)); + this.interpreter.println(java.lang.String.valueOf(arg)); /* Do we want to iterate over iterable things? diff --git a/src/bsh/servlet/SimpleTemplate.java b/src/bsh/servlet/SimpleTemplate.java index bc3a16f..dc6feaa 100644 --- a/src/bsh/servlet/SimpleTemplate.java +++ b/src/bsh/servlet/SimpleTemplate.java @@ -37,7 +37,7 @@ public class SimpleTemplate { StringBuffer buff; static String NO_TEMPLATE = "NO_TEMPLATE"; // Flag for non-existent - static Map templateData = new HashMap(); + static Map templateData = new HashMap(); static boolean cacheTemplates = true; /** @@ -52,7 +52,7 @@ public class SimpleTemplate */ public static SimpleTemplate getTemplate( String file ) { - String templateText = (String)templateData.get( file ); + String templateText = templateData.get( file ); if ( templateText == null || !cacheTemplates ) { try { diff --git a/tests/junitTests/src/bsh/AnnotationsParsing.java b/tests/junitTests/src/bsh/AnnotationsParsing.java new file mode 100644 index 0000000..6dc9d94 --- /dev/null +++ b/tests/junitTests/src/bsh/AnnotationsParsing.java @@ -0,0 +1,24 @@ +package bsh; + +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; + + +/** + * See issue 24. + */ +@RunWith(FilteredTestRunner.class) +public class AnnotationsParsing { + + @Test + @Category(KnownIssue.class) + public void annotation_on_method_declaration() throws Exception { + TestUtil.eval( + "public int myMethod(final int i) {", + " return i * 7;", + "}", + "return myMethod(6);" + ); + } +} diff --git a/tests/junitTests/src/bsh/GoogleReports.java b/tests/junitTests/src/bsh/GoogleReports.java new file mode 100644 index 0000000..e010c3c --- /dev/null +++ b/tests/junitTests/src/bsh/GoogleReports.java @@ -0,0 +1,85 @@ +package bsh; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; + +import static bsh.TestUtil.eval; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(FilteredTestRunner.class) +public class GoogleReports { + + /** + * issue#57 + */ + @Test + @SuppressWarnings({"ConstantIfStatement"}) + public void issue_57() throws Exception { + int loopCount = 0; + do { + loopCount++; + if (true) continue; + } while (false); + assertEquals(1, loopCount); + loopCount = (Integer) eval( + "int loopCount = 0;", + "do{", + " loopCount++;", + " if (loopCount > 100) return loopCount;", + " if (true) continue;", + "} while (false);", + "return loopCount" + ); + assertEquals(1, loopCount); + loopCount = (Integer) eval( + "int loopCount = 0;", + "while (loopCount < 1) {", + " loopCount++;", + " if (loopCount > 100) return loopCount;", + " if (true) continue;", + "}", + "return loopCount" + ); + assertEquals(1, loopCount); + assertEquals(Boolean.TRUE, eval("while(true) { break; return false; } return true;")); + assertEquals(Boolean.TRUE, eval("do { break; return false; } while(true); return true;")); + loopCount = (Integer) eval( + "int loopCount = 0;", + "while (++loopCount < 2);", + "return loopCount" + ); + assertEquals(2, loopCount); + loopCount = (Integer) eval( + "int loopCount = 0;", + "do { } while (++loopCount < 2);", + "return loopCount" + ); + assertEquals(2, loopCount); + } + + + /** + * issue#60 + */ + @Test + public void issue_60() throws Exception { + final String script = + "String foo = null;" + + "if (foo != null && foo.length() > 0) return \"not empty\";" + + "return \"empty\";"; + final ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); + scriptEngineManager.registerEngineName("beanshell", new BshScriptEngineFactory()); + final ScriptEngine engine = scriptEngineManager.getEngineByName("beanshell"); + assertNotNull(engine); + Object result; + result = engine.eval(script); + assertEquals("empty", result); + result = eval(script); + assertEquals("empty", result); + } + +} diff --git a/tests/junitTests/src/bsh/Issue_55_Test.java b/tests/junitTests/src/bsh/Issue_55_Test.java index 08902d9..d614c73 100644 --- a/tests/junitTests/src/bsh/Issue_55_Test.java +++ b/tests/junitTests/src/bsh/Issue_55_Test.java @@ -4,9 +4,12 @@ import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; +import javax.script.ScriptException; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; @RunWith(FilteredTestRunner.class) public class Issue_55_Test { @@ -29,4 +32,16 @@ public void check_ExternalNameSpace() throws Exception { assertNull("variable 'a' should have value ", externalNameSpace.getMap().get("a")); } + @Category( NotSuitedFor_Java5_OrLower.class ) + @Test + public void issue_67() throws Exception { + final String script = "print(\"test\";"; + try { + new BshScriptEngineFactory().getScriptEngine().eval(script); + fail("expected script exception"); + } catch (ScriptException e) { + assertEquals(1, e.getLineNumber()); + } + } + } diff --git a/tests/junitTests/src/bsh/StringLiteralTest.java b/tests/junitTests/src/bsh/StringLiteralTest.java index 3a4d194..b63b298 100644 --- a/tests/junitTests/src/bsh/StringLiteralTest.java +++ b/tests/junitTests/src/bsh/StringLiteralTest.java @@ -72,6 +72,18 @@ public void parse_long_string_literal_multiline() throws Exception { assertStringParsing("test\ntest", DelimiterMode.MULTI_LINE); } + @Test + public void parse_unicode_literals_in_comment() throws Exception { + final Interpreter interpreter = new Interpreter(); + Object result = interpreter.eval("// source path: C:\\user\\desktop"); + Assert.assertEquals(result, null); + char c = (char) interpreter.eval("return '\\u51ea\'"); + Assert.assertEquals(c, '\u51ea'); + String s = (String) interpreter.eval("return \"\\u51EA1234\"; // \\user\\desktop"); + System.out.println(s); + Assert.assertEquals(s, "\u51EA1234"); + } + private void assertStringParsing(final String s, final DelimiterMode mode) throws EvalError { assertStringParsing(s, s, mode);