diff --git a/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java b/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java index a7220082344..39132634c27 100644 --- a/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java +++ b/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java @@ -42,10 +42,10 @@ import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.RightAndLefthandTraffic; import org.openstreetmap.josm.tools.RotationAngle; +import org.openstreetmap.josm.tools.RotationAngle.WayDirectionRotationAngle; import org.openstreetmap.josm.tools.StreamUtils; import org.openstreetmap.josm.tools.Territories; import org.openstreetmap.josm.tools.Utils; -import org.openstreetmap.josm.tools.RotationAngle.WayDirectionRotationAngle; /** * List of functions that can be used in MapCSS expressions. @@ -410,6 +410,9 @@ public static List tag_regex(final Environment env, String keyRegex) { * @since 15315 */ public static List tag_regex(final Environment env, String keyRegex, String flags) { + if (env.osm == null) { + return Collections.emptyList(); + } int f = parse_regex_flags(flags); Pattern compiled = Pattern.compile(keyRegex, f); return env.osm.getKeys().entrySet().stream() @@ -625,7 +628,7 @@ public static double gpx_distance(final Environment env) { * @return {@code true} if the object has a tag with the given key, {@code false} otherwise */ public static boolean has_tag_key(final Environment env, String key) { - return env.osm.hasKey(key); + return env.osm != null ? env.osm.hasKey(key) : false; } /** @@ -940,7 +943,7 @@ public static List regexp_match(String pattern, String target) { * @see IPrimitive#getUniqueId() */ public static long osm_id(final Environment env) { - return env.osm.getUniqueId(); + return env.osm != null ? env.osm.getUniqueId() : 0; } /** @@ -951,7 +954,7 @@ public static long osm_id(final Environment env) { * @since 15246 */ public static String osm_user_name(final Environment env) { - return env.osm.getUser().getName(); + return env.osm != null ? env.osm.getUser().getName() : null; } /** @@ -962,7 +965,7 @@ public static String osm_user_name(final Environment env) { * @since 15246 */ public static long osm_user_id(final Environment env) { - return env.osm.getUser().getId(); + return env.osm != null ? env.osm.getUser().getId() : 0; } /** @@ -973,7 +976,7 @@ public static long osm_user_id(final Environment env) { * @since 15246 */ public static int osm_version(final Environment env) { - return env.osm.getVersion(); + return env.osm != null ? env.osm.getVersion() : 0; } /** @@ -984,7 +987,7 @@ public static int osm_version(final Environment env) { * @since 15246 */ public static int osm_changeset_id(final Environment env) { - return env.osm.getChangesetId(); + return env.osm != null ? env.osm.getChangesetId() : 0; } /** @@ -995,7 +998,7 @@ public static int osm_changeset_id(final Environment env) { * @since 15246 */ public static int osm_timestamp(final Environment env) { - return env.osm.getRawTimestamp(); + return env.osm != null ? env.osm.getRawTimestamp() : 0; } /** @@ -1196,7 +1199,11 @@ public static long CRC32_checksum(String s) { * @since 7193 */ public static boolean is_right_hand_traffic(Environment env) { - return RightAndLefthandTraffic.isRightHandTraffic(center(env)); + final LatLon center = center(env); + if (center != null) { + return RightAndLefthandTraffic.isRightHandTraffic(center); + } + return false; } /** @@ -1260,7 +1267,7 @@ public static Object println(Object o) { * @return number of tags */ public static int number_of_tags(Environment env) { - return env.osm.getNumKeys(); + return env.osm != null ? env.osm.getNumKeys() : 0; } /** @@ -1270,7 +1277,7 @@ public static int number_of_tags(Environment env) { * @return the value of the setting (calculated when the style is loaded) */ public static Object setting(Environment env, String key) { - return env.source.settingValues.get(key); + return env.source != null ? env.source.settingValues.get(key) : null; } /** @@ -1280,7 +1287,12 @@ public static Object setting(Environment env, String key) { * @since 11247 */ public static LatLon center(Environment env) { - return env.osm instanceof Node ? ((Node) env.osm).getCoor() : env.osm.getBBox().getCenter(); + if (env.osm instanceof ILatLon) { + return new LatLon(((ILatLon) env.osm).lat(), ((ILatLon) env.osm).lon()); + } else if (env.osm != null) { + return env.osm.getBBox().getCenter(); + } + return null; } /** @@ -1315,7 +1327,11 @@ public static boolean outside(Environment env, String codes) { * @since 12514 */ public static boolean at(Environment env, double lat, double lon) { - return new LatLon(lat, lon).equalsEpsilon(center(env), ILatLon.MAX_SERVER_PRECISION); + final ILatLon center = center(env); + if (center != null) { + return new LatLon(lat, lon).equalsEpsilon(center, ILatLon.MAX_SERVER_PRECISION); + } + return false; } /** diff --git a/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/FunctionsTest.java b/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/FunctionsTest.java index 496d2c913d4..844e2987f72 100644 --- a/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/FunctionsTest.java +++ b/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/FunctionsTest.java @@ -2,20 +2,28 @@ package org.openstreetmap.josm.gui.mappaint.mapcss; import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.openstreetmap.josm.data.osm.OsmPrimitiveType.NODE; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.openstreetmap.josm.TestUtils; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.IPrimitive; @@ -29,11 +37,13 @@ import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.preferences.NamedColorProperty; import org.openstreetmap.josm.gui.mappaint.Environment; +import org.openstreetmap.josm.gui.mappaint.MultiCascade; import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.testutils.annotations.BasicPreferences; import org.openstreetmap.josm.testutils.annotations.MapPaintStyles; import org.openstreetmap.josm.testutils.annotations.Projection; +import org.openstreetmap.josm.testutils.annotations.Territories; /** * Unit tests of {@link Functions}. @@ -63,6 +73,22 @@ Environment build() { } } + private static Method[] FUNCTIONS; + + static Stream getFunctions() { + if (FUNCTIONS == null) { + FUNCTIONS = Stream.of(Functions.class.getDeclaredMethods()) + .filter(m -> Modifier.isStatic(m.getModifiers()) && Modifier.isPublic(m.getModifiers())) + .toArray(Method[]::new); + } + return Stream.of(FUNCTIONS); + } + + @AfterAll + static void tearDown() { + FUNCTIONS = null; + } + /** * Unit test of {@link Functions#title}. */ @@ -230,4 +256,35 @@ void testParentOsmPrimitives() { assertTrue(Functions.parent_osm_primitives(env, "type2").isEmpty()); } + + /** + * Non-regression test for #23238: NPE when env.osm is null + */ + @ParameterizedTest + @MethodSource("getFunctions") + @Territories // needed for inside, outside, is_right_hand_traffic + void testNonRegression23238(Method function) { + if (function.getParameterCount() >= 1 && function.getParameterTypes()[0].isAssignableFrom(Environment.class) + && !function.getParameterTypes()[0].equals(Object.class)) { + Environment nullOsmEnvironment = new Environment(); + nullOsmEnvironment.mc = new MultiCascade(); + Object[] args = new Object[function.getParameterCount()]; + args[0] = nullOsmEnvironment; + for (int i = 1; i < function.getParameterCount(); i++) { + final Class type = function.getParameterTypes()[i]; + if (String.class.isAssignableFrom(type)) { + args[i] = ""; + } else if (String[].class.isAssignableFrom(type)) { + args[i] = new String[] {"{0}", ""}; // join and tr require at least 2 arguments + } else if (Double.class.isAssignableFrom(type) || double.class.isAssignableFrom(type)) { + args[i] = 0d; + } else if (Object.class.isAssignableFrom(type)) { + args[i] = new Object[0]; + } else { + fail(type.getCanonicalName()); + } + } + assertDoesNotThrow(() -> function.invoke(null, args)); + } + } }