Fix JexlMap and GenotypeJEXLContext NullPtrException #668

Merged
merged 6 commits into from Sep 13, 2016

Conversation

Projects
None yet
4 participants
Contributor

SHuang-Broad commented Jul 24, 2016

Description

  • GenotypeJEXLContext.get() currently potentially throws NullPointerException, triggered by a design choice in Genotype.getFilters(), which explicitly returns null (genotype considered to be filter passing in such case according to documentation). This fix temporarily fixes that exception (1st commit, by Louis).

  • Class CommonInfo, which holds "Common utility routines for VariantContext and Genotype", contains such a constant

    private static Set<String> NO_FILTERS = Collections.emptySet();
    

    VariantContext uses this constant in its own getFilters() (it has a separate method getFiltersMaybeNull()), but Genotype doesn't use such constant. A choice has to be made to whether make Genotype resemble VariantContext's behavior (a backward non-compatible change) or keep things as they are.

  • Some code re-organizing and whole bunch of todo's in JEXLMap, which must be resolved in the review rounds.

Checklist

  • Code compiles correctly
  • New tests covering changes and new functionality
  • All tests passing
  • [PartiallyDone] Extended the README / documentation, if necessary
  • [Possibly] Is not backward compatible (breaks binary or source compatibility)
Contributor

SHuang-Broad commented Jul 24, 2016 edited

@lbergelson , would you mind open discussions on all these?
I don't think this is something that I should decide on my own.

EDIT:
I mean provide your insight, not "open discussions" as tickets...

Coverage Status

Coverage decreased (-0.0002%) to 68.42% when pulling aa23d6f on SHuang-Broad:sh_fix_GenotypeJEXLContext_nullptr into fcfad25 on samtools:master.

Coverage Status

Coverage decreased (-0.01%) to 68.447% when pulling acd4f23 on SHuang-Broad:sh_fix_GenotypeJEXLContext_nullptr into 87b1e87 on samtools:master.

lbergelson was assigned by droazen Aug 9, 2016

droazen referenced this pull request Aug 9, 2016

Open

Review "Party" #548

Coverage Status

Coverage decreased (-0.007%) to 68.8% when pulling 7ac9a57 on SHuang-Broad:sh_fix_GenotypeJEXLContext_nullptr into c8202f2 on samtools:master.

Contributor

SHuang-Broad commented Aug 13, 2016

Sorry @lbergelson , I accidentally pushed another improvements that I'm working on to this branch. Please ignore the third commit first.
Sorry.

@lbergelson lbergelson and 1 other commented on an outdated diff Aug 16, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
* Compatible with JEXL 1.1 (this code will be easier if we move to 2.0, all of the functionality can go into the
* JexlContext's get()
*
+ * TODO: there is some troubling design choices in this class: the laziness and exception behavior:
@lbergelson

lbergelson Aug 16, 2016

Contributor

@SHuang-Broad I think it would be better to open an issue with this comment instead of putting it into the code.

@SHuang-Broad

SHuang-Broad Aug 16, 2016

Contributor

Yes, I just wasn't sure if people would care.

@SHuang-Broad

SHuang-Broad Aug 18, 2016

Contributor

Done, see #686.

@lbergelson lbergelson and 1 other commented on an outdated diff Aug 16, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
// our context
private JexlContext jContext = null;
+ // our variant context and/or Genotype
@lbergelson

lbergelson Aug 16, 2016

Contributor

Why move these down like this? Not a big deal either way but I don't see the reason to change them.

@SHuang-Broad

SHuang-Broad Aug 16, 2016

Contributor

Will change back, thanks!

@lbergelson lbergelson and 1 other commented on an outdated diff Aug 16, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
private Map<JexlVCMatchExp,Boolean> jexl;
-
- public JEXLMap(Collection<JexlVCMatchExp> jexlCollection, VariantContext vc, Genotype g) {
+ // -----------------------------------------------------------------------------------------------
@lbergelson

lbergelson Aug 16, 2016

Contributor

We don't typically have these sorts of big signpost comments. It seems useful for dividing the unsupported operations from the rest, but I'm not sure you need tthem for the rest of the class.

@SHuang-Broad

SHuang-Broad Aug 16, 2016

Contributor

For me personally, it serves the purpose of grouping methods providing similar functionalities in proximity, so that I would jump around to see how one function works. The second reason that I put in these "block segregators" is such that the interfaces exposed and the implementations can be better separated with a quick glance.

If you think this clusters the class instead of helping, I'll remove them.

@SHuang-Broad

SHuang-Broad Aug 18, 2016

Contributor

Removed, but kept the structure of "public first, private later".

@lbergelson lbergelson and 1 other commented on an outdated diff Aug 16, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
/**
- * create the internal JexlContext, only when required. This code is where new JEXL context variables
- * should get added.
- *
+ * Note: due to laziness, this accessor actually modifies the instance by possibly forcing evaluation of an Jexl expression.
+ * @throws IllegalArgumentException when {@code o} is {@code null}
@lbergelson

lbergelson Aug 16, 2016 edited

Contributor

Does this also throw IllegalArgumentException in other cases like you mentioned in the todo? Can you describe those cases?

@SHuang-Broad

SHuang-Broad Aug 18, 2016

Contributor

Done.

@lbergelson lbergelson and 1 other commented on an outdated diff Aug 16, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
* @return a collection of boolean values, representing the results of all the variants evaluated
+ *
+ * @throws IllegalArgumentException
@lbergelson

lbergelson Aug 16, 2016

Contributor

Can you describe when it throws IllegalArgumentException, which is strange since it takes no arguments.

@SHuang-Broad

SHuang-Broad Aug 18, 2016

Contributor

Done.

@lbergelson lbergelson and 1 other commented on an outdated diff Aug 16, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
return jexl.values();
}
/**
- * evaulate a JexlVCMatchExp's expression, given the current context (and setup the context if it's null)
- * @param exp the JexlVCMatchExp to evaluate
+ * TODO: the number may count invalid entries, is this good?
+ * @return the number of keys, i.e. {@link JexlVCMatchExp}'s hold by this mapping.
@lbergelson

lbergelson Aug 16, 2016

Contributor

hold -> held

@lbergelson lbergelson and 1 other commented on an outdated diff Aug 16, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
return jexl.values();
}
/**
- * evaulate a JexlVCMatchExp's expression, given the current context (and setup the context if it's null)
- * @param exp the JexlVCMatchExp to evaluate
+ * TODO: the number may count invalid entries, is this good?
@lbergelson

lbergelson Aug 16, 2016

Contributor

How can you know if they're invalid? By evaluating them and seeing if they throw?

@SHuang-Broad

SHuang-Broad Aug 16, 2016

Contributor

This echoes the "todo" comment at the beginning of this class. There might be no way of knowing if they invalid unless they are evaluated.

@lbergelson lbergelson and 1 other commented on an outdated diff Aug 16, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
+ return jexl.put(jexlVCMatchExp, aBoolean);
+ }
+
+ public void putAll(Map<? extends JexlVCMatchExp, ? extends Boolean> map) {
+ jexl.putAll(map);
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Utilities
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Lazily initializes {@link #jexl}, in the sense that all keys in {@code jexlCollection} are associated with {@code null}.
+ * @param jexlCollection
+ */
+ private void lazyInitialize(Collection<JexlVCMatchExp> jexlCollection) {
@lbergelson

lbergelson Aug 16, 2016

Contributor

I'm not sure this is any clearer than just initialize. Comment might read better as
"Initializes all keys with null values indicating that they have not yet been evaluated. The actual value will be computed only when the key is requested"

@SHuang-Broad

SHuang-Broad Aug 18, 2016

Contributor

Done.

@lbergelson lbergelson and 1 other commented on an outdated diff Aug 16, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
- if (e.getMessage() != null && e.getMessage().contains("undefined variable"))
- jexl.put(exp,false);
- else
- throw new IllegalArgumentException(String.format("Invalid JEXL expression detected for %s with message %s", exp.name, (e.getMessage() == null ? "no message" : e.getMessage())));
+ // if exception happens because variable is undefined (i.e. field in expression is not present), evaluate to FALSE
+ throw new IllegalArgumentException(String.format("Invalid JEXL expression detected for %s with message %s",
+ exp.name,
+ /*(e.getMessage() == null ? */"no message" /*: e.getMessage())*/));
+ }
+ }
+
+ /**
+ * Create the internal JexlContext, only when required.
+ * This code is where new JEXL context variables should get added.
+ */
+ private void createContext() {
@lbergelson

lbergelson Aug 16, 2016

Contributor

I think this was clearer with three distinct cases instead of collapsing one of them.

@SHuang-Broad

SHuang-Broad Aug 18, 2016

Contributor

Done.

@lbergelson lbergelson and 1 other commented on an outdated diff Aug 16, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
* @param infoMap the map
* @param attributes the attributes
*/
- private static void addAttributesToMap(Map<String, Object> infoMap, Map<String, ?> attributes ) {
+ private static void addAttributesToMap(final Map<String, Object> infoMap, final Map<String, ?> attributes ) {
@lbergelson

lbergelson Aug 16, 2016

Contributor

delete this one if it's unused, it's private so nothing else could be using it.

@SHuang-Broad

SHuang-Broad Aug 18, 2016

Contributor

Done.

@lbergelson lbergelson and 1 other commented on an outdated diff Aug 16, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
// //////////////////////////////////////////////////////////////////////////////////////
+ // TODO: is the following comment still true? must resolve in PR review rounds
+ // TODO: is not supported, any other way to resolve at compile time rather than blow up in your face?
@lbergelson

lbergelson Aug 16, 2016

Contributor

These are fine I think. There isn't a good way to convert these to a compile errors since java doesn't have separate interfaces for immutable collections. It's pretty standard to implement some collection functions to just throw UnsupportedOperationException

@SHuang-Broad

SHuang-Broad Aug 18, 2016

Contributor

Got it. Thanks!

@lbergelson lbergelson and 1 other commented on an outdated diff Aug 16, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
try {
final Boolean value = (Boolean) exp.exp.evaluate(jContext);
// treat errors as no match
jexl.put(exp, value == null ? false : value);
- } catch (Exception e) {
+ } catch (final JexlException e) {
@lbergelson

lbergelson Aug 16, 2016

Contributor

In the previous version this handled exceptions that had "undefined variable" differently than other exceptions. I think you should have a separate catch block for JexlException.Variable that does jexl.put(exp,false) and then the catch for generic JexlException that get translated to IllegalArgumentException

@SHuang-Broad

SHuang-Broad Aug 18, 2016

Contributor

Good point! Done.

Coverage Status

Coverage decreased (-0.0002%) to 68.807% when pulling 1ddd123 on SHuang-Broad:sh_fix_GenotypeJEXLContext_nullptr into c8202f2 on samtools:master.

@lbergelson lbergelson and 1 other commented on an outdated diff Aug 19, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
// if exception happens because variable is undefined (i.e. field in expression is not present), evaluate to FALSE
- // todo - might be safer if we explicitly checked for an exception type, but Apache's API doesn't seem to have that ability
- if (e.getMessage() != null && e.getMessage().contains("undefined variable"))
- jexl.put(exp,false);
- else
- throw new IllegalArgumentException(String.format("Invalid JEXL expression detected for %s with message %s", exp.name, (e.getMessage() == null ? "no message" : e.getMessage())));
+ jexl.put(exp,false);
+ } catch (final JexlException e) {
+ // todo - might be better if no exception is caught here but let's user decide how to deal with them; note this will propagate to get() and values()
+ throw new IllegalArgumentException(String.format("Invalid JEXL expression detected for %s with message %s",
@lbergelson

lbergelson Aug 19, 2016

Contributor

could you replace this throw with

throw new IllegalArgumentexception(String.format("Invalid JEXL expression detected for %s", exp.name), e)

so that the underlying cause is propagated more cleanly?

SHuang-Broad changed the title from NO merge yet: Fix JexlMap and GenotypeJEXLContext NullPtrException to Fix JexlMap and GenotypeJEXLContext NullPtrException Aug 22, 2016

@yfarjoun yfarjoun and 1 other commented on an outdated diff Aug 23, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
*/
- private static void addAttributesToMap(Map<String, Object> infoMap, Map<String, ?> attributes ) {
- for (Entry<String, ?> e : attributes.entrySet()) {
- infoMap.put(e.getKey(), String.valueOf(e.getValue()));
+ private void createContext() {
+ if ( vc == null ) {
+ jContext = new MapContext(Collections.emptyMap());
+ }
@yfarjoun

yfarjoun Aug 23, 2016

Contributor

we like to use

} else if {

(here and elsewhere)

@yfarjoun

yfarjoun Aug 23, 2016

Contributor

and

} else {

@yfarjoun yfarjoun and 1 other commented on an outdated diff Aug 23, 2016

...tsjdk/variant/variantcontext/VariantContextUtils.java
*/
public JexlVCMatchExp(String name, Expression exp) {
+ if(name==null){ throw new IllegalArgumentException("Cannot create JexlVCMatchExp with null name."); }
@yfarjoun

yfarjoun Aug 23, 2016

Contributor

spaces:

if␣(name␣==␣null)␣{....

@yfarjoun yfarjoun and 1 other commented on an outdated diff Aug 23, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
*/
- private void createContext() {
- if ( vc == null ) {
- jContext = new MapContext(Collections.emptyMap());
- }
- else if (g == null) {
- jContext = new VariantJEXLContext(vc);
- }
- else {
- jContext = new GenotypeJEXLContext(vc, g);
+ public Boolean get(Object o) {
+ if(o == null){
@yfarjoun

yfarjoun Aug 23, 2016

Contributor

spaces here too

Contributor

lbergelson commented Aug 23, 2016

@SHuang-Broad Can you add a unit test that used to fail with nullptr?

@yfarjoun yfarjoun and 1 other commented on an outdated diff Aug 23, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
// if the context is null, we need to create it to evaluate the JEXL expression
- if (this.jContext == null) createContext();
+ if (this.jContext == null){
@yfarjoun

yfarjoun Aug 23, 2016

Contributor

space before brace

@yfarjoun yfarjoun and 1 other commented on an outdated diff Aug 23, 2016

src/main/java/htsjdk/variant/variantcontext/JEXLMap.java
try {
final Boolean value = (Boolean) exp.exp.evaluate(jContext);
// treat errors as no match
jexl.put(exp, value == null ? false : value);
- } catch (Exception e) {
+ } catch (final JexlException.Variable e){
@yfarjoun

yfarjoun Aug 23, 2016

Contributor

space

Contributor

SHuang-Broad commented Aug 23, 2016

@lbergelson , yes, will add a test.

@SHuang-Broad SHuang-Broad coding style (spaces) change
2cc2b6c

Coverage Status

Coverage increased (+0.006%) to 68.887% when pulling 2cc2b6c on SHuang-Broad:sh_fix_GenotypeJEXLContext_nullptr into a31f6c8 on samtools:master.

@SHuang-Broad SHuang-Broad unit test cleaned up and added more
dd4fc60
Contributor

SHuang-Broad commented Aug 27, 2016

There you go, @lbergelson , the unit test were cleaned up a little and more tests are added.
Thanks!

Coverage Status

Coverage increased (+0.09%) to 68.974% when pulling dd4fc60 on SHuang-Broad:sh_fix_GenotypeJEXLContext_nullptr into a31f6c8 on samtools:master.

@SHuang-Broad SHuang-Broad found how to test Genotype equality and added test case
b67aea1
Contributor

SHuang-Broad commented Sep 7, 2016

Friendly poke on @lbergelson, is the unit test covering what's needed?

Coverage Status

Coverage increased (+0.1%) to 68.996% when pulling b67aea1 on SHuang-Broad:sh_fix_GenotypeJEXLContext_nullptr into a31f6c8 on samtools:master.

Contributor

lbergelson commented Sep 13, 2016

@SHuang-Broad This looks good. Sorry for slowness. 👍

@lbergelson lbergelson merged commit 21d8865 into samtools:master Sep 13, 2016

2 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
coverage/coveralls Coverage increased (+0.1%) to 68.996%
Details

SHuang-Broad deleted the SHuang-Broad:sh_fix_GenotypeJEXLContext_nullptr branch Sep 13, 2016

Contributor

SHuang-Broad commented Sep 13, 2016

Thank you very much! @lbergelson

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment