Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'setClassLoader' with argument 'forceClassLoader' #570

Open
wants to merge 808 commits into
base: master
Choose a base branch
from

Conversation

matthinc
Copy link

The external ClassLoader can be used as a security measure to prevent the execution of unwanted code.
The problem is, that bsh in its current state uses the internal ClassLoader if the external ClassLoader fails. I added an optional forceClassLoader argument to setClassLoader, which prevents bsh from proceeding if the external ClassLoader fails.

 * implement the JDK7, aka  project coin, number literal additions
 * add binary number literals ex. 0b010101
 * correctly parsing and processing underscores in numeric literals
WeakHashMap would either loose or not correctly set the namespace in multi thread conditions.
Instead lets use ConcurrentHashMap and manually remove the entries on retrieval.
It was not implemented anymore so remove it.
Removed in favour of JSR223 support.
BSF engine moved to external module bsh-bsf-engine for anyone that might still require it.
The field attribute is not serializable so when writing object we drop the field and retain the declared class, when needed again we fetch the field from the class again.
 * test is successful if we remove the assert(false);
 * removed additional junit test not required
 * remove as known issue
 * rename to BshScriptTestCase and inner class TestBshScript to Script
 * add Script constructor for String filename in test script folder
 * simplify setting interpreter "bsh.cwd" variable
 * properly allow Fail.bsh to fail and assert expected error
 * add ability to skip tests based on assumptions
We'll assume we haveAccessibility before attempting to test illegal access, this will skip these tests if accessibility=false (default)
In some cases static imports were failing now we look for the class first before leaving it to Name to try and resolve.
Even with setAccessible package private constructors and methods were failing, we now make constructors and methods without modifiers public and super delegates are public by default as they will have any additional modifiers applied when calling the super method.
JDK8+ does not have rt.jar anymore so we just add an empty boot class path.
load command does not need to setAccessible anymore.
compile and run tests with accessibility=false (default) now succeeds with --illegal-access=deny with Java 9 or Java 10
It is not clear what was originally intended with this script it fails for lack of a default constructor on the anonymous class. The current state of anonymous class capability is uncertain and since this is called classinner and not classanonymous I added something more appropriate to test for.
Grouped some more scripts with the InnerClassTest test case for reference.
This test was failing on the if statement Capabilities.classExists("bsh.ExternalNameSpace") for some reason. Added a static import and changed the if block to an assume and all the asserts passed.
Refactored the test to use junit asserts seemed more appropriate.
 * removed some commented lines
 * ph-javacc-maven-plugin version 4.x uses the new javacc fork ParserGeneratorCC see https://github.com/phax/ParserGeneratorCC
 * TokenMgrError was made redundant by ParserGeneratorCC so we remove it as well
 * added bsh.debugClasses as a system property for specifying the location to save generated class files.
 * for null type we give preference to String, then most specific non primitive which is not an array, then arrays
 * less specific is assignable by most specific unless signature is a match
 * improved varargs matching no argument or null type as vararg
 * known issue RefectTest.findMostSpecificSignature elaborated and resolved
 * added some reusability improvements to testUtil
 * renamed and redistributed issue_XX tests to more intuitive names.
Collected from generically named unit tests.
Only contains the switch string statement now.
 * added new grammar for try TryWithResources and AutoCloseable
 * implemented BSHAutoCloseable as a subclass of BSHTypedVariableDeclaration to reuse the variable evaluation.
 * BSHTryWithResources acts as the collection of resources and interface for evaluating and closing the resources.
 * added a lookup for 1st Block node to BSHTryStatement to avoid the use of magic numbers
 * on eval start if the 1st Node is TryWithResources we keep a reference and call eval which will create the variables in the namespace in callstack
 * in the finally block of the try block evaluation, if we have a reference to BSHTryWithResources we auto close, which ensures that all resources are closed.
 * suppressed on close exceptions will be collected in the target exception from the try block
* Infer a collection of parameter names and set a collection of parameter modifiers for the BshMethod wrapped java.reflect.Method delegation.
Java 9/10 handles the close exceptions differently to JDK 8, with the latter the exceptions thrown on close are the same instance as the target exception with suppressed exception already collected. With the newer versions you have to add the suppressions manually.
 * add grammar to parse multi catch
 * implement evaluation of catch block with a list of types
 * improve check assignability to avoid the previous try fail catch continue manoeuvres
 * enable ability to throw throwable and subtypes - previously limited to exception types only
 * added some simple multi catch tests
We are loosing state because some nodes don't have modifiers.
* remove redundant use of token variables
 * remove redundant comments
 * some styling ease of readability
default behaviour ,regardless of accessibility, if no access modifiers (or package private) bsh default is public visibility
 * validate abstract methods are implemented or class declared abstract
 * bsh classes are virtual abstract only or iow abstract classes can be instantiated
 * apply inheritance rules overridden methods may not reduce visibility
nickl- and others added 10 commits June 25, 2019 03:22
Added additional eval only constructors for Interpreter which uses the system standard output and standard error but defers input to be supplied by eval.
Refactored eval method to extract reusable method for terminating statement strings.
Simplified the implementation of the PreparsedScript class, simple interpreter instance apply class loader to interpreter, eval the script to retrieve the callable object and reference the prepared instance.
On invoke create the scoped name space and local interpreter, set the context variables invoke the prepared script and unwrap the results.
Added compound name resolution in context variable names.
Standardised the source file info and do not reveal the wrapped method name.
Added terminated script conversion to ensure statement is terminated fixes  beanshell#529
Fixed unit tests and enabled multi threaded test, added additional tests to illustrate compound name resolution.
Added serial garbage collector java vm argument to properly apply large eden algorithms which parallel gc will stall on.
Added an empty map and a map of key value pair list helper to TestUtil for supplying tests with data maps.
Changes as per enhancements made to StringUtil for data type values to string.
Fixes beanshell#527
Performance: O(1) Listener add/delete
Fix for beanshell#566 cannot concat assign null string.
It was not immediately clear what the purpose of checkNullValues was so refactored the implementation.
Optimised if statements to release as soon as possible by finding most likely to fail first.
Found and fixed bug with null comparison with wrapper types not working like Java. Now allows for comparing 3 states of Boolean types.
Reduce operators required stack memory footprint.
@codecov
Copy link

codecov bot commented Oct 13, 2019

Codecov Report

Merging #570 into master will decrease coverage by 0.03%.
The diff coverage is 100.00%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master     #570      +/-   ##
============================================
- Coverage     70.44%   70.40%   -0.04%     
+ Complexity     2695     2694       -1     
============================================
  Files           106      106              
  Lines          8945     8948       +3     
  Branches       1765     1765              
============================================
- Hits           6301     6300       -1     
- Misses         2215     2217       +2     
- Partials        429      431       +2     
Impacted Files Coverage Δ Complexity Δ
src/main/java/bsh/BshClassManager.java 64.67% <ø> (-1.50%) 22.00 <0.00> (-1.00)
src/main/java/bsh/classpath/ClassManagerImpl.java 64.11% <100.00%> (-0.56%) 38.00 <0.00> (-1.00)
src/main/java/bsh/commands/dir.java 58.49% <0.00%> (-1.89%) 4.00% <0.00%> (ø%)
src/main/java/bsh/Name.java 85.58% <0.00%> (+0.58%) 117.00% <0.00%> (+1.00%)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6014e15...be47004. Read the comment docs.

Copy link
Member

@nickl- nickl- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome thanx for the PR.

Made some suggestions please see review comments.

// If forceExternalClassLoader is true, there is no reason to return a class here
if (forceExternalClassLoader) {
return null;
}
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do the check once on this line and return c;

Also I'd prefer one line statements without braces please.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also we need to hook the cache storage here before returning.

// Cache result (or null for not found)
cacheClassInfo( name, c );

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the caching. The brace suggestion is obsolete, because I need more lines now.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

video call for the core 2

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what time

@@ -546,8 +552,20 @@ protected void clearCaches() {
However BeanShell is not currently able to reload
classes supplied through the external classloader.
*/
public void setClassLoader( ClassLoader externalCL ) {
public void setClassLoader(ClassLoader externalCL) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to avoid white space changes, it makes it easier to review.

We will apply a uniform code style at a later stage.

public void testExternalClassManagerOptional() throws EvalError {
final Interpreter interpreter = new Interpreter();
interpreter.setClassLoader(testLoader);
interpreter.eval("String tst = \"Hello, World!\";");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No assertion in test.

Sure you are testing that it doesn't fail but that is not clear from inspecting the test case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert that Class.getClassLoader() doesn't equal the same instance as proof.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps also test for Thread.currentThread().getContextClassLoader()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rewrote all the tests. They work with try/catch now. You're right - they were hard to understand.

src/test/java/bsh/classpath/ExternalClassLoaderTest.java Outdated Show resolved Hide resolved
@Override
public Class<?> loadClass(final String name) throws ClassNotFoundException {
if (name.equals("java.lang.String")) {
throw new ClassNotFoundException();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some more thought I don't like this.

Throwing exceptions is expensive and a much more thorough test would be to return null.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, but I have also seen people, who used a ClassNotFoundException.
I now have both - null and the exception as a test-case.

public void testExternalClassManagerForced() throws EvalError {
final Interpreter interpreter = new Interpreter();
interpreter.setClassLoader(testLoader, true);
interpreter.eval("String tst = \"Hello, World!\";");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eval can return tst for assert.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It came to me, that a String is not a good test case, because it's handled differently than other classes by java. I now use 'System' and 'Math' - they should be much better test-cases.

src/main/java/bsh/classpath/ClassManagerImpl.java Outdated Show resolved Hide resolved
.travis.yml Outdated Show resolved Hide resolved
@nickl-
Copy link
Member

nickl- commented Nov 13, 2019

I've given this some more thought, why is this so complicated? Now we're complicating it even more.

Can't we choose a class loader on initialize, first from Thread.currentThread().getContextClassLoader(); then from this.getClass().getClassLoader() (which is what forName() will use anyway) if it fails. Otherwise use the one specified by setClassLoader(). If you want to load a class from source file you can explicitly source the file with interpreter before use.

Doesn't that make more sense?

@matthinc
Copy link
Author

I've given this some more thought, why is this so complicated? Now we're complicating it even more.

Can't we choose a class loader on initialize, first from Thread.currentThread().getContextClassLoader(); then from this.getClass().getClassLoader() (which is what forName() will use anyway) if it fails. Otherwise use the one specified by setClassLoader(). If you want to load a class from source file you can explicitly source the file with interpreter before use.

Doesn't that make more sense?

Hi @nickl-,
I agree with you that the current logic is pretty complicated. The way how the setClassLoader works now is confusing and could be even dangerous in some situations.
My goal was to force the use of a ClassLoader without breaking any existing code which depends on bsh.
Alternatively, I could change the behavior, that setClassLoader aways forces the use of the ClassLoader and clean up the existing logic. But that would possibly break some projects.

What would you suggest?

@nickl-
Copy link
Member

nickl- commented Jan 18, 2020

Alternatively, I could change the behavior, that setClassLoader aways forces the use of the ClassLoader and clean up the existing logic. But that would possibly break some projects.

What would you suggest?

I think we should rework the process by simplifying as suggested. Give it a try and if it breaks anything, which I doubt it will, then lets take it from there.

Include more unit tests if code coverage is not sufficient.

@matthinc
Copy link
Author

Thank you for all the feedback!
I applied your suggestions to my PR

@nickl-
Copy link
Member

nickl- commented Jan 7, 2023

@matthinc
I tried to rebase this on a separate branch but it looks to be too outdated. Please rework if you don't want to loose the commits or I will have to manually apply the changes to master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet