Comparisons with the NULL literal (x != NULL) crash the compiler with NullPointerExceptions instead of a readable error
Environment
- lsFusion 7.0-SNAPSHOT (server build 392)
- Java 17.0.9, PostgreSQL 18, Windows 10
- reproduced via module load (startup / dry run) and via the
/eval HTTP endpoint
Context
Per language semantics, any comparison with a NULL operand propagates NULL, so an expression like expr != NULL is always NULL — it is a user error. The problem is that the platform does not reject it with a readable message: depending on where the expression is used, it either crashes the compiler with a raw NullPointerException, or (worst case) compiles cleanly and silently always evaluates to NULL.
Repro A — != NULL inside FORM FILTERS → NPE at module load
CLASS Item;
price = DATA INTEGER (Item);
FORM items 'Items'
OBJECTS i = Item
PROPERTIES(i) price
FILTERS price(i) != NULL
;
Loading this module (server start / dry run) fails with:
java.lang.NullPointerException: Cannot invoke "lsfusion.base.col.interfaces.immutable.ImMap.get(Object)" because "<parameter1>" is null
at lsfusion.server.logics.property.classes.infer.ExClassSet.lambda$0(ExClassSet.java:48)
at lsfusion.server.logics.property.classes.infer.ExClassSet.op(ExClassSet.java:48)
at lsfusion.server.logics.property.classes.infer.ExClassSet.intersect(ExClassSet.java:44)
at lsfusion.server.logics.property.Property.intersect(Property.java:354)
at lsfusion.server.logics.property.Property.inferIsInInterface(Property.java:595)
at lsfusion.server.logics.property.classes.infer.InferType.isInInterface(InferType.java:53)
at lsfusion.server.logics.property.Property.isInInterface(Property.java:576)
at lsfusion.server.logics.form.struct.ScriptingFormEntity.checkPropertyParameters(ScriptingFormEntity.java:501)
at lsfusion.server.logics.form.struct.ScriptingFormEntity.addScriptedFilters(ScriptingFormEntity.java:778)
at lsfusion.server.language.LsfLogicsParser.formFiltersList(LsfLogicsParser.java:10722)
...
The same expression posted through the /eval endpoint crashes the eval compiler with the same NPE.
Repro B — != NULL inside EXPORT FROM → NPE at script compile
EXPORT FROM a = (price(Item i) != NULL);
(any property compared to the NULL literal as an aliased EXPORT FROM expression)
java.lang.NullPointerException: Cannot invoke "Object.equals(Object)" because the return value of "lsfusion.server.logics.property.Property.getType()" is null
at lsfusion.server.logics.property.PropertyFact.createNotNull(PropertyFact.java:1310)
at lsfusion.server.logics.property.PropertyFact.createUnionNotNull(PropertyFact.java:1087)
at lsfusion.server.logics.property.PropertyFact.getFullWhereProperty(PropertyFact.java:1080)
at lsfusion.server.logics.property.PropertyFact.getFullWhereProperty(PropertyFact.java:1093)
at lsfusion.server.logics.LogicsModule.addIntegrationForm(LogicsModule.java:792)
at lsfusion.server.logics.LogicsModule.addExportPropertyAProp(LogicsModule.java:838)
at lsfusion.server.logics.language.ScriptingLogicsModule.addScriptedExportAction(ScriptingLogicsModule.java:4976)
...
The sneakier variant — compiles cleanly, silently always NULL
The same comparison used as a plain module property does not crash at all:
isSet (Item i) = price(i) != NULL; // always NULL, no error, no warning
It compiles, and every dependent filter/condition silently never fires — no diagnostic anywhere. This is arguably worse than the NPEs: I hit this in a real project (isScheduled, hasNotes, search matchers written as x != NULL AND …) and the only symptom was silently empty lists.
Expected behavior
- A readable compile-time error or warning wherever an equality/inequality operand is the
NULL literal, e.g. "expr != NULL is always NULL by NULL-propagation; use IF expr THEN TRUE for a non-null check or NOT expr for a null check".
- No
NullPointerException from the parser/inference paths (Repro A and B) — a user mistake in an expression should surface as a message, not a stack trace.
Since the pattern (= NULL / != NULL against the literal) is statically recognizable, a single check in the expression compiler would cover all three variants above.
Workaround
isSet (Item i) = IF price(i) THEN TRUE; // non-null check
isNull (Item i) = NOT price(i); // null check
Comparisons with the NULL literal (
x != NULL) crash the compiler with NullPointerExceptions instead of a readable errorEnvironment
/evalHTTP endpointContext
Per language semantics, any comparison with a
NULLoperand propagatesNULL, so an expression likeexpr != NULLis always NULL — it is a user error. The problem is that the platform does not reject it with a readable message: depending on where the expression is used, it either crashes the compiler with a rawNullPointerException, or (worst case) compiles cleanly and silently always evaluates toNULL.Repro A —
!= NULLinsideFORM FILTERS→ NPE at module loadLoading this module (server start / dry run) fails with:
The same expression posted through the
/evalendpoint crashes the eval compiler with the same NPE.Repro B —
!= NULLinsideEXPORT FROM→ NPE at script compile(any property compared to the NULL literal as an aliased EXPORT FROM expression)
The sneakier variant — compiles cleanly, silently always NULL
The same comparison used as a plain module property does not crash at all:
It compiles, and every dependent filter/condition silently never fires — no diagnostic anywhere. This is arguably worse than the NPEs: I hit this in a real project (
isScheduled,hasNotes, search matchers written asx != NULL AND …) and the only symptom was silently empty lists.Expected behavior
NULLliteral, e.g. "expr != NULLis always NULL by NULL-propagation; useIF expr THEN TRUEfor a non-null check orNOT exprfor a null check".NullPointerExceptionfrom the parser/inference paths (Repro A and B) — a user mistake in an expression should surface as a message, not a stack trace.Since the pattern (
= NULL/!= NULLagainst the literal) is statically recognizable, a single check in the expression compiler would cover all three variants above.Workaround