Skip to content

Comparisons with the NULL literal (x != NULL) crash the compiler with NPEs instead of a readable error #1807

Description

@CrushBy

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

  1. 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".
  2. 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

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions