LUCENE-7800: Remove code that potentially rethrows checked exceptions from methods that don't declare them ("sneaky throw" hack).

This commit is contained in:
Dawid Weiss 2017-05-18 16:38:53 +02:00
parent c9bdce937a
commit 5a50887a4b
4 changed files with 94 additions and 102 deletions

View File

@ -97,6 +97,12 @@ Other
======================= Lucene 6.7.0 =======================
Other
* LUCENE-7800: Remove code that potentially rethrows checked exceptions
from methods that don't declare them ("sneaky throw" hack). (Robert Muir,
Uwe Schindler, Dawid Weiss)
======================= Lucene 6.6.0 =======================
New Features

View File

@ -31,6 +31,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package org.tartarus.snowball;
import java.lang.reflect.UndeclaredThrowableException;
import org.apache.lucene.util.ArrayUtil;
/**
@ -313,8 +315,10 @@ public abstract class SnowballProgram {
boolean res = false;
try {
res = (boolean) w.method.invokeExact(this);
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable e) {
rethrow(e);
throw new UndeclaredThrowableException(e);
}
cursor = c + w.s_size;
if (res) return w.result;
@ -376,8 +380,10 @@ public abstract class SnowballProgram {
boolean res = false;
try {
res = (boolean) w.method.invokeExact(this);
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable e) {
rethrow(e);
throw new UndeclaredThrowableException(e);
}
cursor = c - w.s_size;
if (res) return w.result;
@ -485,15 +491,5 @@ extern void debug(struct SN_env * z, int number, int line_count)
printf("'\n");
}
*/
// Hack to rethrow unknown Exceptions from {@link MethodHandle#invoke}:
private static void rethrow(Throwable t) {
SnowballProgram.<Error>rethrow0(t);
}
@SuppressWarnings("unchecked")
private static <T extends Throwable> void rethrow0(Throwable t) throws T {
throw (T) t;
}
};

View File

@ -20,6 +20,7 @@ package org.apache.lucene.util;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.UndeclaredThrowableException;
/**
* An AttributeFactory creates instances of {@link AttributeImpl}s.
@ -28,8 +29,14 @@ public abstract class AttributeFactory {
/**
* Returns an {@link AttributeImpl} for the supplied {@link Attribute} interface class.
*
* @throws UndeclaredThrowableException A wrapper runtime exception thrown if the
* constructor of the attribute class throws a checked exception.
* Note that attributes should not throw or declare
* checked exceptions; this may be verified and fail early in the future.
*/
public abstract AttributeImpl createAttributeInstance(Class<? extends Attribute> attClass);
public abstract AttributeImpl createAttributeInstance(Class<? extends Attribute> attClass)
throws UndeclaredThrowableException;
/**
* Returns a correctly typed {@link MethodHandle} for the no-arg ctor of the given class.
@ -66,9 +73,10 @@ public abstract class AttributeFactory {
public AttributeImpl createAttributeInstance(Class<? extends Attribute> attClass) {
try {
return (AttributeImpl) constructors.get(attClass).invokeExact();
} catch (Throwable t) {
rethrow(t);
throw new AssertionError();
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@ -138,23 +146,12 @@ public abstract class AttributeFactory {
protected A createInstance() {
try {
return (A) constr.invokeExact();
} catch (Throwable t) {
rethrow(t);
throw new AssertionError();
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
};
}
// Hack to rethrow unknown Exceptions from {@link MethodHandle#invoke}:
// TODO: remove the impl in test-framework, this one is more elegant :-)
static void rethrow(Throwable t) {
AttributeFactory.<Error>rethrow0(t);
}
@SuppressWarnings("unchecked")
private static <T extends Throwable> void rethrow0(Throwable t) throws T {
throw (T) t;
}
}

View File

@ -189,14 +189,19 @@ public final class JavascriptCompiler {
final Map<String, Integer> externalsMap = new LinkedHashMap<>();
final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
try {
generateClass(getAntlrParseTree(), classWriter, externalsMap);
try {
final Class<? extends Expression> evaluatorClass = new Loader(parent)
.define(COMPILED_EXPRESSION_CLASS, classWriter.toByteArray());
final Constructor<? extends Expression> constructor = evaluatorClass.getConstructor(String.class, String[].class);
return constructor.newInstance(sourceText, externalsMap.keySet().toArray(new String[externalsMap.size()]));
} catch (RuntimeException re) {
if (re.getCause() instanceof ParseException) {
throw (ParseException)re.getCause();
}
throw re;
} catch (ReflectiveOperationException exception) {
throw new IllegalStateException("An internal error occurred attempting to compile the expression (" + sourceText + ").", exception);
}
@ -209,7 +214,6 @@ public final class JavascriptCompiler {
* @throws ParseException on failure to parse
*/
private ParseTree getAntlrParseTree() throws ParseException {
try {
final ANTLRInputStream antlrInputStream = new ANTLRInputStream(sourceText);
final JavascriptErrorHandlingLexer javascriptLexer = new JavascriptErrorHandlingLexer(antlrInputStream);
javascriptLexer.removeErrorListeners();
@ -217,12 +221,6 @@ public final class JavascriptCompiler {
javascriptParser.removeErrorListeners();
javascriptParser.setErrorHandler(new JavascriptParserErrorStrategy());
return javascriptParser.compile();
} catch (RuntimeException re) {
if (re.getCause() instanceof ParseException) {
throw (ParseException)re.getCause();
}
throw re;
}
}
/**
@ -291,14 +289,15 @@ public final class JavascriptCompiler {
boolean parens = ctx.LP() != null && ctx.RP() != null;
Method method = parens ? functions.get(text) : null;
try {
if (method != null) {
int arity = method.getParameterTypes().length;
if (arguments != arity) {
throwChecked(new ParseException(
throw new ParseException(
"Invalid expression '" + sourceText + "': Expected (" +
arity + ") arguments for function call (" + text + "), but found (" + arguments + ").",
ctx.start.getStartIndex()));
ctx.start.getStartIndex());
}
typeStack.push(Type.DOUBLE_TYPE);
@ -331,11 +330,15 @@ public final class JavascriptCompiler {
gen.invokeVirtual(FUNCTION_VALUES_TYPE, DOUBLE_VAL_METHOD);
gen.cast(Type.DOUBLE_TYPE, typeStack.peek());
} else {
throwChecked(new ParseException("Invalid expression '" + sourceText + "': Unrecognized function call (" +
text + ").", ctx.start.getStartIndex()));
throw new ParseException("Invalid expression '" + sourceText + "': Unrecognized function call (" +
text + ").", ctx.start.getStartIndex());
}
return null;
} catch (ParseException e) {
// The API doesn't allow checked exceptions here, so propagate up the stack. This is unwrapped
// in getAntlrParseTree.
throw new RuntimeException(e);
}
}
@Override
@ -623,16 +626,6 @@ public final class JavascriptCompiler {
throw new IllegalStateException("Invalid expected type: " + typeStack.peek());
}
}
/** Needed to throw checked ParseException in this visitor (that does not allow it). */
private void throwChecked(Throwable t) {
this.<Error>throwChecked0(t);
}
@SuppressWarnings("unchecked")
private <T extends Throwable> void throwChecked0(Throwable t) throws T {
throw (T) t;
}
}.visit(parseTree);
gen.returnValue();