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 ======================= ======================= 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 ======================= ======================= Lucene 6.6.0 =======================
New Features New Features

View File

@ -31,6 +31,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package org.tartarus.snowball; package org.tartarus.snowball;
import java.lang.reflect.UndeclaredThrowableException;
import org.apache.lucene.util.ArrayUtil; import org.apache.lucene.util.ArrayUtil;
/** /**
@ -313,8 +315,10 @@ public abstract class SnowballProgram {
boolean res = false; boolean res = false;
try { try {
res = (boolean) w.method.invokeExact(this); res = (boolean) w.method.invokeExact(this);
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable e) { } catch (Throwable e) {
rethrow(e); throw new UndeclaredThrowableException(e);
} }
cursor = c + w.s_size; cursor = c + w.s_size;
if (res) return w.result; if (res) return w.result;
@ -376,8 +380,10 @@ public abstract class SnowballProgram {
boolean res = false; boolean res = false;
try { try {
res = (boolean) w.method.invokeExact(this); res = (boolean) w.method.invokeExact(this);
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable e) { } catch (Throwable e) {
rethrow(e); throw new UndeclaredThrowableException(e);
} }
cursor = c - w.s_size; cursor = c - w.s_size;
if (res) return w.result; if (res) return w.result;
@ -485,15 +491,5 @@ extern void debug(struct SN_env * z, int number, int line_count)
printf("'\n"); 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.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.lang.reflect.UndeclaredThrowableException;
/** /**
* An AttributeFactory creates instances of {@link AttributeImpl}s. * 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. * 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. * 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) { public AttributeImpl createAttributeInstance(Class<? extends Attribute> attClass) {
try { try {
return (AttributeImpl) constructors.get(attClass).invokeExact(); return (AttributeImpl) constructors.get(attClass).invokeExact();
} catch (Throwable t) { } catch (Error | RuntimeException e) {
rethrow(t); throw e;
throw new AssertionError(); } catch (Throwable e) {
throw new UndeclaredThrowableException(e);
} }
} }
@ -138,23 +146,12 @@ public abstract class AttributeFactory {
protected A createInstance() { protected A createInstance() {
try { try {
return (A) constr.invokeExact(); return (A) constr.invokeExact();
} catch (Throwable t) { } catch (Error | RuntimeException e) {
rethrow(t); throw e;
throw new AssertionError(); } 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 Map<String, Integer> externalsMap = new LinkedHashMap<>();
final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
try {
generateClass(getAntlrParseTree(), classWriter, externalsMap); generateClass(getAntlrParseTree(), classWriter, externalsMap);
try {
final Class<? extends Expression> evaluatorClass = new Loader(parent) final Class<? extends Expression> evaluatorClass = new Loader(parent)
.define(COMPILED_EXPRESSION_CLASS, classWriter.toByteArray()); .define(COMPILED_EXPRESSION_CLASS, classWriter.toByteArray());
final Constructor<? extends Expression> constructor = evaluatorClass.getConstructor(String.class, String[].class); final Constructor<? extends Expression> constructor = evaluatorClass.getConstructor(String.class, String[].class);
return constructor.newInstance(sourceText, externalsMap.keySet().toArray(new String[externalsMap.size()])); 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) { } catch (ReflectiveOperationException exception) {
throw new IllegalStateException("An internal error occurred attempting to compile the expression (" + sourceText + ").", 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 * @throws ParseException on failure to parse
*/ */
private ParseTree getAntlrParseTree() throws ParseException { private ParseTree getAntlrParseTree() throws ParseException {
try {
final ANTLRInputStream antlrInputStream = new ANTLRInputStream(sourceText); final ANTLRInputStream antlrInputStream = new ANTLRInputStream(sourceText);
final JavascriptErrorHandlingLexer javascriptLexer = new JavascriptErrorHandlingLexer(antlrInputStream); final JavascriptErrorHandlingLexer javascriptLexer = new JavascriptErrorHandlingLexer(antlrInputStream);
javascriptLexer.removeErrorListeners(); javascriptLexer.removeErrorListeners();
@ -217,12 +221,6 @@ public final class JavascriptCompiler {
javascriptParser.removeErrorListeners(); javascriptParser.removeErrorListeners();
javascriptParser.setErrorHandler(new JavascriptParserErrorStrategy()); javascriptParser.setErrorHandler(new JavascriptParserErrorStrategy());
return javascriptParser.compile(); 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; boolean parens = ctx.LP() != null && ctx.RP() != null;
Method method = parens ? functions.get(text) : null; Method method = parens ? functions.get(text) : null;
try {
if (method != null) { if (method != null) {
int arity = method.getParameterTypes().length; int arity = method.getParameterTypes().length;
if (arguments != arity) { if (arguments != arity) {
throwChecked(new ParseException( throw new ParseException(
"Invalid expression '" + sourceText + "': Expected (" + "Invalid expression '" + sourceText + "': Expected (" +
arity + ") arguments for function call (" + text + "), but found (" + arguments + ").", arity + ") arguments for function call (" + text + "), but found (" + arguments + ").",
ctx.start.getStartIndex())); ctx.start.getStartIndex());
} }
typeStack.push(Type.DOUBLE_TYPE); typeStack.push(Type.DOUBLE_TYPE);
@ -331,11 +330,15 @@ public final class JavascriptCompiler {
gen.invokeVirtual(FUNCTION_VALUES_TYPE, DOUBLE_VAL_METHOD); gen.invokeVirtual(FUNCTION_VALUES_TYPE, DOUBLE_VAL_METHOD);
gen.cast(Type.DOUBLE_TYPE, typeStack.peek()); gen.cast(Type.DOUBLE_TYPE, typeStack.peek());
} else { } else {
throwChecked(new ParseException("Invalid expression '" + sourceText + "': Unrecognized function call (" + throw new ParseException("Invalid expression '" + sourceText + "': Unrecognized function call (" +
text + ").", ctx.start.getStartIndex())); text + ").", ctx.start.getStartIndex());
} }
return null; 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 @Override
@ -623,16 +626,6 @@ public final class JavascriptCompiler {
throw new IllegalStateException("Invalid expected type: " + typeStack.peek()); 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); }.visit(parseTree);
gen.returnValue(); gen.returnValue();