@@ -161,6 +161,66 @@ class Expr extends @expr, ExprOrStmt, ExprOrType, AST::ValueNode {
161161 * file that was extracted without type information.
162162 */
163163 Type getType ( ) { ast_node_type ( this , result ) }
164+
165+ /**
166+ * Holds if the syntactic context that the expression appears in relies on the expression
167+ * being non-null/non-undefined.
168+ *
169+ * A context relies on the subexpression being non-null/non-undefined if either...
170+ *
171+ * * Using null or undefined would cause a runtime error
172+ * * Using null or undefined would cause no error due to type conversion, but the
173+ * behavior in the broader context is sufficiently non-obvious to warrant explicitly
174+ * converting to ensure that readers understand the intent
175+ */
176+ predicate inNullSensitiveContext ( ) {
177+ exists ( ExprOrStmt ctx |
178+ // bases cases
179+ this = ctx .( PropAccess ) .getBase ( )
180+ or
181+ this = ctx .( IndexExpr ) .getPropertyNameExpr ( )
182+ or
183+ this = ctx .( InvokeExpr ) .getCallee ( )
184+ or
185+ this = ctx .( BinaryExpr ) .getAnOperand ( ) and
186+ not ctx instanceof LogicalBinaryExpr and // x LOGOP y is fine because of implicit conversion
187+ not ctx instanceof EqualityTest and // x EQOP y is fine because of implicit conversion and lack thereof
188+ not ctx .( BitOrExpr ) .getAnOperand ( ) .( NumberLiteral ) .getIntValue ( ) = 0 and // x | 0 is fine because it's used to convert to numbers
189+ not ctx .( RShiftExpr ) .getRightOperand ( ) .( NumberLiteral ) .getIntValue ( ) = 0 and // x >> 0 is fine because it's used to convert to numbers
190+ not ctx .( URShiftExpr ) .getRightOperand ( ) .( NumberLiteral ) .getIntValue ( ) = 0 // x >>> 0 is fine because it's used to convert to numbers
191+ or
192+ this = ctx .( UnaryExpr ) .getOperand ( ) and
193+ not ctx instanceof LogNotExpr and // !x is fine because of implicit conversion
194+ not ctx instanceof PlusExpr and // +x is fine because of implicit conversion
195+ not ctx instanceof VoidExpr // void x is fine because it explicitly is for capturing void things
196+ or
197+ this = ctx .( UpdateExpr ) .getOperand ( )
198+ or
199+ this = ctx .( CompoundAssignExpr ) .getLhs ( )
200+ or
201+ this = ctx .( CompoundAssignExpr ) .getRhs ( )
202+ or
203+ this = ctx .( AssignExpr ) .getRhs ( ) and
204+ ctx .( AssignExpr ) .getLhs ( ) instanceof DestructuringPattern
205+ or
206+ this = ctx .( SpreadElement ) .getOperand ( )
207+ or
208+ this = ctx .( ForOfStmt ) .getIterationDomain ( )
209+ or
210+ // recursive cases
211+ this = ctx .( ParExpr ) .getExpression ( ) and
212+ ctx .( ParExpr ) .inNullSensitiveContext ( )
213+ or
214+ this = ctx .( SeqExpr ) .getLastOperand ( ) and
215+ ctx .( SeqExpr ) .inNullSensitiveContext ( )
216+ or
217+ this = ctx .( LogicalBinaryExpr ) .getRightOperand ( ) and
218+ ctx .( LogicalBinaryExpr ) .inNullSensitiveContext ( )
219+ or
220+ this = ctx .( ConditionalExpr ) .getABranch ( ) and
221+ ctx .( ConditionalExpr ) .inNullSensitiveContext ( )
222+ )
223+ }
164224}
165225
166226/** An identifier. */
0 commit comments