From 4c5570cbef26f40ebe5b383ae5ab9b35fa50b5ca Mon Sep 17 00:00:00 2001 From: Martin Kellogg Date: Fri, 23 Sep 2022 11:51:53 -0400 Subject: [PATCH 1/8] non-minimized test case demonstrating the crash --- .../tests/ainfer-index/non-annotated/Pom.java | 556 ++++++++++++++++++ 1 file changed, 556 insertions(+) create mode 100644 checker/tests/ainfer-index/non-annotated/Pom.java diff --git a/checker/tests/ainfer-index/non-annotated/Pom.java b/checker/tests/ainfer-index/non-annotated/Pom.java new file mode 100644 index 00000000000..bbfa7daacf8 --- /dev/null +++ b/checker/tests/ainfer-index/non-annotated/Pom.java @@ -0,0 +1,556 @@ + +import java.io.File; +import java.io.InputStream; +import java.io.PrintStream; +import java.io.Serializable; +import java.sql.SQLException; +import java.util.*; +import java.util.Iterator; +import java.util.Set; + +public class Pom { + + // These are now 0-20, 21-40, 41-60, 61-80, 81+ but filenames unchanged for compatibility + private static final String HEALTH_OVER_80 = "icon-health-80plus"; + private static final String HEALTH_61_TO_80 = "icon-health-60to79"; + private static final String HEALTH_41_TO_60 = "icon-health-40to59"; + private static final String HEALTH_21_TO_40 = "icon-health-20to39"; + private static final String HEALTH_0_TO_20 = "icon-health-00to19"; + + private static final String HEALTH_OVER_80_IMG = "health-80plus.png"; + private static final String HEALTH_61_TO_80_IMG = "health-60to79.png"; + private static final String HEALTH_41_TO_60_IMG = "health-40to59.png"; + private static final String HEALTH_21_TO_40_IMG = "health-20to39.png"; + private static final String HEALTH_0_TO_20_IMG = "health-00to19.png"; + private String iconClassName; + protected transient /*almost final*/ RunMap runs = new RunMap(); + private transient boolean notLoaded = true; + private transient long nextUpdate = 0; + private transient volatile boolean reloadingInProgress; + private static ReloadThread reloadThread; + //private static org.jruby.ext.posix.POSIX jnaPosix; + private boolean ignoreBase; + + //ADDED BY KOBI + public void runAll() { + new HealthReport(1, "iconUrl", new Localizable()); + new ViewJob()._getRuns(); + // get(); + limit(null, null); + } + + //SNIPPET_STARTS + public class HealthReport implements Serializable, Comparable { + private String iconClassName; + private int score; + private String iconUrl; + private transient String description; + private Localizable localizibleDescription; + + public void setLocalizibleDescription(Localizable localizibleDescription) { + this.localizibleDescription = localizibleDescription; + } + + @Override + public int compareTo(HealthReport o) { + return 0; + } + + // hudson.model.HealthReport.HealthReport(int,java.lang.String,org.jvnet.localizer.Localizable) + /** + * Create a new HealthReport. + * + * @param score The percentage health score (from 0 to 100 inclusive). + * @param iconUrl The path to the icon corresponding to this 's health or null to + * display the default icon corresponding to the current health score. + *

+ * If the path begins with a '/' then it will be the absolute path, otherwise the image is + * assumed to be in one of /images/16x16/, /images/24x24/ or + * /images/32x32/ depending on the icon size selected by the user. + * When calculating the url to display for absolute paths, the getIconUrl(String) method + * will replace /32x32/ in the path with the appropriate size. + * @param description The health icon's tool-tip. + */ + public HealthReport(int score, String iconUrl, Localizable description) { + this.score = score; + if (score <= 20) { + this.iconClassName = HEALTH_0_TO_20; + } else if (score <= 40) { + this.iconClassName = HEALTH_21_TO_40; + } else if (score <= 60) { + this.iconClassName = HEALTH_41_TO_60; + } else if (score <= 80) { + this.iconClassName = HEALTH_61_TO_80; + } else { + this.iconClassName = HEALTH_OVER_80; + } + if (iconUrl == null) { + if (score <= 20) { + this.iconUrl = HEALTH_0_TO_20_IMG; + } else if (score <= 40) { + this.iconUrl = HEALTH_21_TO_40_IMG; + } else if (score <= 60) { + this.iconUrl = HEALTH_41_TO_60_IMG; + } else if (score <= 80) { + this.iconUrl = HEALTH_61_TO_80_IMG; + } else { + this.iconUrl = HEALTH_OVER_80_IMG; + } + } else { + this.iconUrl = iconUrl; + } + this.description = null; + setLocalizibleDescription(description); + } + } + + //SNIPPET_STARTS + private class ViewJob { + // hudson.model.ViewJob._getRuns() + protected SortedMap _getRuns() { + if(notLoaded || runs==null) { + // if none is loaded yet, do so immediately. + synchronized(this) { + if(runs==null) + runs = new RunMap(); + if(notLoaded) { + notLoaded = false; + _reload(); + } + } + } + if(nextUpdate reloadQueue; + synchronized (ViewJob.class) { + if (reloadThread == null) { + reloadThread = new ReloadThread(); + reloadThread.start(); + } + reloadQueue = reloadThread.reloadQueue; + } + synchronized(reloadQueue) { + reloadQueue.add(this); + reloadQueue.notify(); + } + } + } + return runs; + } + } + + // hudson.os.PosixAPI.get() + //SNIPPET_STARTS + /*@Deprecated + public static synchronized org.jruby.ext.posix.POSIX get() { + if (jnaPosix == null) { + jnaPosix = org.jruby.ext.posix.POSIXFactory.getPOSIX(new Pom.POSIXHandler() { // Change POSIXHandler signature + public void error(ERRORS errors, String s) throws PosixException { // changed ERRORS signature, added throws + throw new PosixException(s,errors); + } + + public void unimplementedError(String s) { + throw new UnsupportedOperationException(s); + } + + public void warn(WARNING_ID warning_id, String s, Object... objects) { + LOGGER.fine(s); + } + + public boolean isVerbose() { + return true; + } + + public File getCurrentWorkingDirectory() { + return new File(".").getAbsoluteFile(); + } + + public String[] getEnv() { + Map envs = System.getenv(); + String[] envp = new String[envs.size()]; + + int i = 0; + for (Map.Entry e : envs.entrySet()) { + envp[i++] = e.getKey()+'+'+e.getValue(); + } + return envp; + } + + public InputStream getInputStream() { + return System.in; + } + + public PrintStream getOutputStream() { + return System.out; + } + + public int getPID() { + // TODO + return 0; + } + + public PrintStream getErrorStream() { + return System.err; + } + }, true); + } + return jnaPosix; + }*/ + + // hudson.util.Iterators.limit(java.util.Iterator,hudson.util.Iterators.CountingPredicate) + /** + * Returns the elements in the base iterator until it hits any element that doesn't satisfy the filter. + * Then the rest of the elements in the base iterator gets ignored. + * + * @since 1.485 + */ + //SNIPPET_STARTS + public static Iterator limit(final Iterator base, final CountingPredicate filter) { + return new Iterator() { + private T next; + private boolean end; + private int index=0; + public boolean hasNext() { + fetch(); + return next!=null; + } + + public T next() { + fetch(); + T r = next; + next = null; + return r; + } + + private void fetch() { + if (next==null && !end) { + if (base.hasNext()) { + next = base.next(); + if (!filter.apply(index++,next)) { + next = null; + end = true; + } + } else { + end = true; + } + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + // jenkins.util.AntClassLoader.loadClass(java.lang.String,boolean) + /** + * Loads a class with this class loader. + * + * This class attempts to load the class in an order determined by whether + * or not the class matches the system/loader package lists, with the + * loader package list taking priority. If the classloader is in isolated + * mode, failure to load the class in this loader will result in a + * ClassNotFoundException. + * + * @param classname The name of the class to be loaded. + * Must not be null. + * @param resolve true if all classes upon which this class + * depends are to be loaded. + * + * @return the required Class object + * + * @exception ClassNotFoundException if the requested class does not exist + * on the system classpath (when not in isolated mode) or this loader's + * classpath. + */ + //SNIPPET_STARTS + protected synchronized Class loadClass(String classname, boolean resolve) + throws ClassNotFoundException { + // 'sync' is needed - otherwise 2 threads can load the same class + // twice, resulting in LinkageError: duplicated class definition. + // findLoadedClass avoids that, but without sync it won't work. + + Class theClass = findLoadedClass(classname); + if (theClass != null) { + return theClass; + } + if (isParentFirst(classname)) { + try { + theClass = findBaseClass(classname); + log("Class " + classname + " loaded from parent loader " + "(parentFirst)", + Project.MSG_DEBUG); + } catch (ClassNotFoundException cnfe) { + theClass = findClass(classname); + log("Class " + classname + " loaded from ant loader " + "(parentFirst)", + Project.MSG_DEBUG); + } + } else { + try { + theClass = findClass(classname); + log("Class " + classname + " loaded from ant loader", Project.MSG_DEBUG); + } catch (ClassNotFoundException cnfe) { + if (ignoreBase) { + throw cnfe; + } + theClass = findBaseClass(classname); + log("Class " + classname + " loaded from parent loader", Project.MSG_DEBUG); + } + } + if (resolve) { + resolveClass(theClass); + } + return theClass; + } + //SNIPPETS_END + + private Class findClass(String classname) throws ClassNotFoundException{ + return null; + } + + private boolean isParentFirst(String classname) { + return false; + } + + private Class findBaseClass(String classname) throws ClassNotFoundException{ + return null; + } + + private void log(String s, Object msgDebug) { + + } + + private void resolveClass(Class theClass) { + + } + + private Class findLoadedClass(String classname) { + return null; + } + + private static class ReloadThread { + public Set reloadQueue; + + public void start() { + + } + } + + private void _reload() { + + } + private class Localizable { + } + + private class RunT { + } + + private class RunMap implements SortedMap { + @Override + public Comparator comparator() { + return null; + } + + @Override + public SortedMap subMap(Object fromKey, Object toKey) { + return null; + } + + @Override + public SortedMap headMap(Object toKey) { + return null; + } + + @Override + public SortedMap tailMap(Object fromKey) { + return null; + } + + @Override + public Object firstKey() { + return null; + } + + @Override + public Object lastKey() { + return null; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean containsKey(Object key) { + return false; + } + + @Override + public boolean containsValue(Object value) { + return false; + } + + @Override + public Object get(Object key) { + return null; + } + + @Override + public Object put(Object key, Object value) { + return null; + } + + @Override + public Object remove(Object key) { + return null; + } + + @Override + public void putAll(Map m) { + + } + + @Override + public void clear() { + + } + + @Override + public Set keySet() { + return null; + } + + @Override + public Collection values() { + return null; + } + + @Override + public Set entrySet() { + return null; + } + + @Override + public boolean equals(Object o) { + return false; + } + + @Override + public int hashCode() { + return 0; + } + } + + private static class LOGGER { + public static void fine(String s) { + + } + } + + + + private static class PosixException extends Throwable { + public PosixException(String s, ERRORS errors) { + } + } + + private static class Errno { } + + private static class ERRORS extends Errno { + } + + public static class POSIXHandler /*implements org.jruby.ext.posix.POSIXHandler*/ { + //@Override + public void error(Errno errno, String s) { + + } + + //@Override + public void unimplementedError(String s) { + + } + + //@Override + public void warn(WARNING_ID warning_id, String s, Object... objects) { + + } + + //@Override + public boolean isVerbose() { + return false; + } + + //@Override + public File getCurrentWorkingDirectory() { + return null; + } + + //@Override + public String[] getEnv() { + return new String[0]; + } + + //@Override + public InputStream getInputStream() { + return null; + } + + //@Override + public PrintStream getOutputStream() { + return null; + } + + //@Override + public int getPID() { + return 0; + } + + //@Override + public PrintStream getErrorStream() { + return null; + } + + public void debug(String s, Object jdbcUrl) { + + } + + public void debug(String no_events_to_process_) { + + } + + public void error(String exception_while_persisting_to_hbase_, SQLException e) { + + } + + public void error(String s, Throwable e) { + + } + + public void info(String format) { + + } + + public void error(String s, String keyGeneratorType, Object values) { + + } + } + + private static class CountingPredicate { + public boolean apply(int i, T next) { + return false; + } + } + + private static class Project { + public static final Object MSG_DEBUG = ""; + } +} From 6366b2e1853914eeee77df79c32a68484ffee1a4 Mon Sep 17 00:00:00 2001 From: Martin Kellogg Date: Fri, 23 Sep 2022 17:10:19 -0400 Subject: [PATCH 2/8] minimize the test and a sketchy fix --- .../non-annotated/Dataset6Crash.java | 58 ++ .../tests/ainfer-index/non-annotated/Pom.java | 556 ------------------ .../WholeProgramInferenceImplementation.java | 12 +- 3 files changed, 69 insertions(+), 557 deletions(-) create mode 100644 checker/tests/ainfer-index/non-annotated/Dataset6Crash.java delete mode 100644 checker/tests/ainfer-index/non-annotated/Pom.java diff --git a/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java b/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java new file mode 100644 index 00000000000..e4984e9e07f --- /dev/null +++ b/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java @@ -0,0 +1,58 @@ +// Test case for a WPI crash caused by mismatches between captured type variables +// and the declared type of a field: in particular, the issue is that base.next() +// the next field actually have slightly different types: base.next()'s type is +// a capture that extends T. + +import java.util.Iterator; + +public class Dataset6Crash { + + public static Iterator limit(final Iterator base, final CountingPredicate filter) { + return new Iterator() { + + private T next; + + private boolean end; + + private int index = 0; + + public boolean hasNext() { + return true; + } + + public T next() { + fetch(); + T r = next; + next = null; + return r; + } + + private void fetch() { + if (next == null && !end) { + if (base.hasNext()) { + next = base.next(); + if (!filter.apply(index++, next)) { + next = null; + end = true; + } + } else { + end = true; + } + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + + private static class CountingPredicate { + public boolean apply(int i, T next) { + return false; + } + } +} + diff --git a/checker/tests/ainfer-index/non-annotated/Pom.java b/checker/tests/ainfer-index/non-annotated/Pom.java deleted file mode 100644 index bbfa7daacf8..00000000000 --- a/checker/tests/ainfer-index/non-annotated/Pom.java +++ /dev/null @@ -1,556 +0,0 @@ - -import java.io.File; -import java.io.InputStream; -import java.io.PrintStream; -import java.io.Serializable; -import java.sql.SQLException; -import java.util.*; -import java.util.Iterator; -import java.util.Set; - -public class Pom { - - // These are now 0-20, 21-40, 41-60, 61-80, 81+ but filenames unchanged for compatibility - private static final String HEALTH_OVER_80 = "icon-health-80plus"; - private static final String HEALTH_61_TO_80 = "icon-health-60to79"; - private static final String HEALTH_41_TO_60 = "icon-health-40to59"; - private static final String HEALTH_21_TO_40 = "icon-health-20to39"; - private static final String HEALTH_0_TO_20 = "icon-health-00to19"; - - private static final String HEALTH_OVER_80_IMG = "health-80plus.png"; - private static final String HEALTH_61_TO_80_IMG = "health-60to79.png"; - private static final String HEALTH_41_TO_60_IMG = "health-40to59.png"; - private static final String HEALTH_21_TO_40_IMG = "health-20to39.png"; - private static final String HEALTH_0_TO_20_IMG = "health-00to19.png"; - private String iconClassName; - protected transient /*almost final*/ RunMap runs = new RunMap(); - private transient boolean notLoaded = true; - private transient long nextUpdate = 0; - private transient volatile boolean reloadingInProgress; - private static ReloadThread reloadThread; - //private static org.jruby.ext.posix.POSIX jnaPosix; - private boolean ignoreBase; - - //ADDED BY KOBI - public void runAll() { - new HealthReport(1, "iconUrl", new Localizable()); - new ViewJob()._getRuns(); - // get(); - limit(null, null); - } - - //SNIPPET_STARTS - public class HealthReport implements Serializable, Comparable { - private String iconClassName; - private int score; - private String iconUrl; - private transient String description; - private Localizable localizibleDescription; - - public void setLocalizibleDescription(Localizable localizibleDescription) { - this.localizibleDescription = localizibleDescription; - } - - @Override - public int compareTo(HealthReport o) { - return 0; - } - - // hudson.model.HealthReport.HealthReport(int,java.lang.String,org.jvnet.localizer.Localizable) - /** - * Create a new HealthReport. - * - * @param score The percentage health score (from 0 to 100 inclusive). - * @param iconUrl The path to the icon corresponding to this 's health or null to - * display the default icon corresponding to the current health score. - *

- * If the path begins with a '/' then it will be the absolute path, otherwise the image is - * assumed to be in one of /images/16x16/, /images/24x24/ or - * /images/32x32/ depending on the icon size selected by the user. - * When calculating the url to display for absolute paths, the getIconUrl(String) method - * will replace /32x32/ in the path with the appropriate size. - * @param description The health icon's tool-tip. - */ - public HealthReport(int score, String iconUrl, Localizable description) { - this.score = score; - if (score <= 20) { - this.iconClassName = HEALTH_0_TO_20; - } else if (score <= 40) { - this.iconClassName = HEALTH_21_TO_40; - } else if (score <= 60) { - this.iconClassName = HEALTH_41_TO_60; - } else if (score <= 80) { - this.iconClassName = HEALTH_61_TO_80; - } else { - this.iconClassName = HEALTH_OVER_80; - } - if (iconUrl == null) { - if (score <= 20) { - this.iconUrl = HEALTH_0_TO_20_IMG; - } else if (score <= 40) { - this.iconUrl = HEALTH_21_TO_40_IMG; - } else if (score <= 60) { - this.iconUrl = HEALTH_41_TO_60_IMG; - } else if (score <= 80) { - this.iconUrl = HEALTH_61_TO_80_IMG; - } else { - this.iconUrl = HEALTH_OVER_80_IMG; - } - } else { - this.iconUrl = iconUrl; - } - this.description = null; - setLocalizibleDescription(description); - } - } - - //SNIPPET_STARTS - private class ViewJob { - // hudson.model.ViewJob._getRuns() - protected SortedMap _getRuns() { - if(notLoaded || runs==null) { - // if none is loaded yet, do so immediately. - synchronized(this) { - if(runs==null) - runs = new RunMap(); - if(notLoaded) { - notLoaded = false; - _reload(); - } - } - } - if(nextUpdate reloadQueue; - synchronized (ViewJob.class) { - if (reloadThread == null) { - reloadThread = new ReloadThread(); - reloadThread.start(); - } - reloadQueue = reloadThread.reloadQueue; - } - synchronized(reloadQueue) { - reloadQueue.add(this); - reloadQueue.notify(); - } - } - } - return runs; - } - } - - // hudson.os.PosixAPI.get() - //SNIPPET_STARTS - /*@Deprecated - public static synchronized org.jruby.ext.posix.POSIX get() { - if (jnaPosix == null) { - jnaPosix = org.jruby.ext.posix.POSIXFactory.getPOSIX(new Pom.POSIXHandler() { // Change POSIXHandler signature - public void error(ERRORS errors, String s) throws PosixException { // changed ERRORS signature, added throws - throw new PosixException(s,errors); - } - - public void unimplementedError(String s) { - throw new UnsupportedOperationException(s); - } - - public void warn(WARNING_ID warning_id, String s, Object... objects) { - LOGGER.fine(s); - } - - public boolean isVerbose() { - return true; - } - - public File getCurrentWorkingDirectory() { - return new File(".").getAbsoluteFile(); - } - - public String[] getEnv() { - Map envs = System.getenv(); - String[] envp = new String[envs.size()]; - - int i = 0; - for (Map.Entry e : envs.entrySet()) { - envp[i++] = e.getKey()+'+'+e.getValue(); - } - return envp; - } - - public InputStream getInputStream() { - return System.in; - } - - public PrintStream getOutputStream() { - return System.out; - } - - public int getPID() { - // TODO - return 0; - } - - public PrintStream getErrorStream() { - return System.err; - } - }, true); - } - return jnaPosix; - }*/ - - // hudson.util.Iterators.limit(java.util.Iterator,hudson.util.Iterators.CountingPredicate) - /** - * Returns the elements in the base iterator until it hits any element that doesn't satisfy the filter. - * Then the rest of the elements in the base iterator gets ignored. - * - * @since 1.485 - */ - //SNIPPET_STARTS - public static Iterator limit(final Iterator base, final CountingPredicate filter) { - return new Iterator() { - private T next; - private boolean end; - private int index=0; - public boolean hasNext() { - fetch(); - return next!=null; - } - - public T next() { - fetch(); - T r = next; - next = null; - return r; - } - - private void fetch() { - if (next==null && !end) { - if (base.hasNext()) { - next = base.next(); - if (!filter.apply(index++,next)) { - next = null; - end = true; - } - } else { - end = true; - } - } - } - - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - // jenkins.util.AntClassLoader.loadClass(java.lang.String,boolean) - /** - * Loads a class with this class loader. - * - * This class attempts to load the class in an order determined by whether - * or not the class matches the system/loader package lists, with the - * loader package list taking priority. If the classloader is in isolated - * mode, failure to load the class in this loader will result in a - * ClassNotFoundException. - * - * @param classname The name of the class to be loaded. - * Must not be null. - * @param resolve true if all classes upon which this class - * depends are to be loaded. - * - * @return the required Class object - * - * @exception ClassNotFoundException if the requested class does not exist - * on the system classpath (when not in isolated mode) or this loader's - * classpath. - */ - //SNIPPET_STARTS - protected synchronized Class loadClass(String classname, boolean resolve) - throws ClassNotFoundException { - // 'sync' is needed - otherwise 2 threads can load the same class - // twice, resulting in LinkageError: duplicated class definition. - // findLoadedClass avoids that, but without sync it won't work. - - Class theClass = findLoadedClass(classname); - if (theClass != null) { - return theClass; - } - if (isParentFirst(classname)) { - try { - theClass = findBaseClass(classname); - log("Class " + classname + " loaded from parent loader " + "(parentFirst)", - Project.MSG_DEBUG); - } catch (ClassNotFoundException cnfe) { - theClass = findClass(classname); - log("Class " + classname + " loaded from ant loader " + "(parentFirst)", - Project.MSG_DEBUG); - } - } else { - try { - theClass = findClass(classname); - log("Class " + classname + " loaded from ant loader", Project.MSG_DEBUG); - } catch (ClassNotFoundException cnfe) { - if (ignoreBase) { - throw cnfe; - } - theClass = findBaseClass(classname); - log("Class " + classname + " loaded from parent loader", Project.MSG_DEBUG); - } - } - if (resolve) { - resolveClass(theClass); - } - return theClass; - } - //SNIPPETS_END - - private Class findClass(String classname) throws ClassNotFoundException{ - return null; - } - - private boolean isParentFirst(String classname) { - return false; - } - - private Class findBaseClass(String classname) throws ClassNotFoundException{ - return null; - } - - private void log(String s, Object msgDebug) { - - } - - private void resolveClass(Class theClass) { - - } - - private Class findLoadedClass(String classname) { - return null; - } - - private static class ReloadThread { - public Set reloadQueue; - - public void start() { - - } - } - - private void _reload() { - - } - private class Localizable { - } - - private class RunT { - } - - private class RunMap implements SortedMap { - @Override - public Comparator comparator() { - return null; - } - - @Override - public SortedMap subMap(Object fromKey, Object toKey) { - return null; - } - - @Override - public SortedMap headMap(Object toKey) { - return null; - } - - @Override - public SortedMap tailMap(Object fromKey) { - return null; - } - - @Override - public Object firstKey() { - return null; - } - - @Override - public Object lastKey() { - return null; - } - - @Override - public int size() { - return 0; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public boolean containsKey(Object key) { - return false; - } - - @Override - public boolean containsValue(Object value) { - return false; - } - - @Override - public Object get(Object key) { - return null; - } - - @Override - public Object put(Object key, Object value) { - return null; - } - - @Override - public Object remove(Object key) { - return null; - } - - @Override - public void putAll(Map m) { - - } - - @Override - public void clear() { - - } - - @Override - public Set keySet() { - return null; - } - - @Override - public Collection values() { - return null; - } - - @Override - public Set entrySet() { - return null; - } - - @Override - public boolean equals(Object o) { - return false; - } - - @Override - public int hashCode() { - return 0; - } - } - - private static class LOGGER { - public static void fine(String s) { - - } - } - - - - private static class PosixException extends Throwable { - public PosixException(String s, ERRORS errors) { - } - } - - private static class Errno { } - - private static class ERRORS extends Errno { - } - - public static class POSIXHandler /*implements org.jruby.ext.posix.POSIXHandler*/ { - //@Override - public void error(Errno errno, String s) { - - } - - //@Override - public void unimplementedError(String s) { - - } - - //@Override - public void warn(WARNING_ID warning_id, String s, Object... objects) { - - } - - //@Override - public boolean isVerbose() { - return false; - } - - //@Override - public File getCurrentWorkingDirectory() { - return null; - } - - //@Override - public String[] getEnv() { - return new String[0]; - } - - //@Override - public InputStream getInputStream() { - return null; - } - - //@Override - public PrintStream getOutputStream() { - return null; - } - - //@Override - public int getPID() { - return 0; - } - - //@Override - public PrintStream getErrorStream() { - return null; - } - - public void debug(String s, Object jdbcUrl) { - - } - - public void debug(String no_events_to_process_) { - - } - - public void error(String exception_while_persisting_to_hbase_, SQLException e) { - - } - - public void error(String s, Throwable e) { - - } - - public void info(String format) { - - } - - public void error(String s, String keyGeneratorType, Object values) { - - } - } - - private static class CountingPredicate { - public boolean apply(int i, T next) { - return false; - } - } - - private static class Project { - public static final Object MSG_DEBUG = ""; - } -} diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java index e47b81e8ecd..78b1d5f28b2 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java @@ -804,7 +804,17 @@ protected void updateAnnotationSet( AnnotatedTypeMirror atmFromStorage = storage.atmFromStorageLocation(rhsATM.getUnderlyingType(), annotationsToUpdate); - updateAtmWithLub(rhsATM, atmFromStorage); + // It is possible that rhsATM is a type variable but atmFromStorage is not, which + // would cause a ClassCastException in updateAtmWithLub(). This can + // happen for example when a local variable of type ? extends T is assigned to a + // field of type T, such as in the test ainfer-index/non-annotated/Dataset6Crash.java. + try { + updateAtmWithLub(rhsATM, atmFromStorage); + } catch (ClassCastException c) { + AnnotatedTypeMirror compatibleRHSAtm = + AnnotatedTypes.asSuper(this.atypeFactory, rhsATM, atmFromStorage); + updateAtmWithLub(compatibleRHSAtm, atmFromStorage); + } if (lhsATM instanceof AnnotatedTypeVariable) { Set upperAnnos = ((AnnotatedTypeVariable) lhsATM).getUpperBound().getEffectiveAnnotations(); From ef3942b792ebc0646d8e3d1bedd75ff6d918d623 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Fri, 23 Sep 2022 14:39:49 -0700 Subject: [PATCH 3/8] Remove new line. --- checker/tests/ainfer-index/non-annotated/Dataset6Crash.java | 1 - 1 file changed, 1 deletion(-) diff --git a/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java b/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java index e4984e9e07f..9ebc71e441e 100644 --- a/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java +++ b/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java @@ -55,4 +55,3 @@ public boolean apply(int i, T next) { } } } - From 515da1d1a0441c8564262695267a3c76ffc51c23 Mon Sep 17 00:00:00 2001 From: Martin Kellogg Date: Mon, 26 Sep 2022 10:30:38 -0400 Subject: [PATCH 4/8] debug code --- .../WholeProgramInferenceImplementation.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java index c720e9e9791..50d489c7d05 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java @@ -811,6 +811,15 @@ protected void updateAnnotationSet( try { updateAtmWithLub(rhsATM, atmFromStorage); } catch (ClassCastException c) { + System.out.println("Catching a ClassCastException: " + c); + System.out.println( + "The cast must be one of the following two AnnotatedTypeMirrors. The" + + " type to cast to is decided by the kind of rhsATM."); + System.out.println("rhsATM: " + rhsATM); + System.out.println("rhsATM.getKind(): " + rhsATM.getKind()); + System.out.println("atmFromStorage: " + atmFromStorage); + System.out.println("atmFromStorage.getKind(): " + atmFromStorage.getKind()); + AnnotatedTypeMirror compatibleRHSAtm = AnnotatedTypes.asSuper(this.atypeFactory, rhsATM, atmFromStorage); updateAtmWithLub(compatibleRHSAtm, atmFromStorage); From f4131a3a30d6719ad629b260e0522f502b17b737 Mon Sep 17 00:00:00 2001 From: Martin Kellogg Date: Mon, 26 Sep 2022 10:31:15 -0400 Subject: [PATCH 5/8] fix wording --- .../WholeProgramInferenceImplementation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java index 50d489c7d05..8e5a91d6950 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java @@ -806,7 +806,7 @@ protected void updateAnnotationSet( storage.atmFromStorageLocation(rhsATM.getUnderlyingType(), annotationsToUpdate); // It is possible that rhsATM is a type variable but atmFromStorage is not, which // would cause a ClassCastException in updateAtmWithLub(). This can - // happen for example when a local variable of type ? extends T is assigned to a + // happen for example when an expression of type ? extends T is assigned to a // field of type T, such as in the test ainfer-index/non-annotated/Dataset6Crash.java. try { updateAtmWithLub(rhsATM, atmFromStorage); From 11807c5180460204de2acb904599a4e721cf937d Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Mon, 26 Sep 2022 09:41:22 -0700 Subject: [PATCH 6/8] Add new test case. --- .../tests/ainfer-index/non-annotated/TypeVarAssignment.java | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 checker/tests/ainfer-index/non-annotated/TypeVarAssignment.java diff --git a/checker/tests/ainfer-index/non-annotated/TypeVarAssignment.java b/checker/tests/ainfer-index/non-annotated/TypeVarAssignment.java new file mode 100644 index 00000000000..c30a27ec384 --- /dev/null +++ b/checker/tests/ainfer-index/non-annotated/TypeVarAssignment.java @@ -0,0 +1,6 @@ +public class TypeVarAssignment { +T t; +void foo(S s) { + t = s; + } +} From 41745833a64a3acfbbc5c6c3a3890a51ea8d9b2b Mon Sep 17 00:00:00 2001 From: Martin Kellogg Date: Wed, 28 Sep 2022 10:27:36 -0400 Subject: [PATCH 7/8] better fix based on Suzanne's insight --- .../WholeProgramInferenceImplementation.java | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java index b1ebb3b5078..b2b6432501f 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java @@ -815,26 +815,7 @@ protected void updateAnnotationSet( AnnotatedTypeMirror atmFromStorage = storage.atmFromStorageLocation(rhsATM.getUnderlyingType(), annotationsToUpdate); - // It is possible that rhsATM is a type variable but atmFromStorage is not, which - // would cause a ClassCastException in updateAtmWithLub(). This can - // happen for example when an expression of type ? extends T is assigned to a - // field of type T, such as in the test ainfer-index/non-annotated/Dataset6Crash.java. - try { - updateAtmWithLub(rhsATM, atmFromStorage); - } catch (ClassCastException c) { - System.out.println("Catching a ClassCastException: " + c); - System.out.println( - "The cast must be one of the following two AnnotatedTypeMirrors. The" - + " type to cast to is decided by the kind of rhsATM."); - System.out.println("rhsATM: " + rhsATM); - System.out.println("rhsATM.getKind(): " + rhsATM.getKind()); - System.out.println("atmFromStorage: " + atmFromStorage); - System.out.println("atmFromStorage.getKind(): " + atmFromStorage.getKind()); - - AnnotatedTypeMirror compatibleRHSAtm = - AnnotatedTypes.asSuper(this.atypeFactory, rhsATM, atmFromStorage); - updateAtmWithLub(compatibleRHSAtm, atmFromStorage); - } + updateAtmWithLub(rhsATM, atmFromStorage); if (lhsATM instanceof AnnotatedTypeVariable) { Set upperAnnos = ((AnnotatedTypeVariable) lhsATM).getUpperBound().getEffectiveAnnotations(); @@ -877,6 +858,14 @@ private void printFailedInferenceDebugMessage(String reason) { */ private void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror ajavaATM) { + if (sourceCodeATM.getKind() != ajavaATM.getKind()) { + // This can happen e.g. when recursing into the bounds of a type variable: + // the bound on sourceCodeATM might be a declared type (such as T), while + // the ajavaATM might be a typevar (such as S extends T), or vice-versa. In + // that case, use asSuper to make the two ATMs fully-compatible. + sourceCodeATM = AnnotatedTypes.asSuper(this.atypeFactory, sourceCodeATM, ajavaATM); + } + switch (sourceCodeATM.getKind()) { case TYPEVAR: updateAtmWithLub( From 7787d463320bc43a9bcc23530e2fd7869d3ceb25 Mon Sep 17 00:00:00 2001 From: Martin Kellogg Date: Wed, 28 Sep 2022 13:43:57 -0400 Subject: [PATCH 8/8] fix a bug I'd introduced with respect to nullness handling, which also makes a prior check unnecessary --- checker/build.gradle | 46 +++++++++++++++++++ .../AinferNullnessAjavaTest.java | 40 ++++++++++++++++ .../AinferNullnessAjavaValidationTest.java | 37 +++++++++++++++ .../non-annotated/NullTypeVarTest.java | 32 +++++++++++++ .../WholeProgramInferenceImplementation.java | 36 +++++---------- 5 files changed, 166 insertions(+), 25 deletions(-) create mode 100644 checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaTest.java create mode 100644 checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaValidationTest.java create mode 100644 checker/tests/ainfer-nullness/non-annotated/NullTypeVarTest.java diff --git a/checker/build.gradle b/checker/build.gradle index 88d5c31dbf4..77e8196d78f 100644 --- a/checker/build.gradle +++ b/checker/build.gradle @@ -706,6 +706,52 @@ task ainferIndexAjavaTest(dependsOn: 'shadowJar', group: 'Verification') { outputs.upToDateWhen { false } } +task ainferNullnessGenerateAjava(type: Test) { + description 'Internal task. Users should run ainferNullnessAjavaTest instead. This type-checks the ainfer-nullness files with -Ainfer=ajava to generate ajava files.' + + dependsOn(compileTestJava) + doFirst { + delete("tests/ainfer-nullness/annotated") + delete("${buildDir}/ainfer-nullness/") + } + outputs.upToDateWhen { false } + include '**/AinferNullnessAjavaTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat "full" + events "passed", "skipped", "failed" + } + + doLast { + copyNonannotatedToAnnotatedDirectory("ainfer-nullness") + } +} + +task ainferNullnessValidateAjava(type: Test) { + description 'Internal task. Users should run ainferNullnessAjavaTest instead. This re-type-checks the ainfer-nullness files using the ajava files generated by ainferNullnessGenerateAjava' + + dependsOn(ainferNullnessGenerateAjava) + outputs.upToDateWhen { false } + include '**/AinferNullnessAjavaValidationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat "full" + events "passed", "skipped", "failed" + } +} + +task ainferNullnessAjavaTest(dependsOn: 'shadowJar', group: 'Verification') { + description 'Run tests for whole-program inference using ajava files and the Nullness Checker' + dependsOn(ainferNullnessValidateAjava) + outputs.upToDateWhen { false } +} + task ainferNullnessGenerateJaifs(type: Test) { description 'Internal task. Users should run ainferNullnessJaifTest instead. This type-checks the ainfer-nullness files with -Ainfer=jaifs to generate .jaif files' diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaTest.java new file mode 100644 index 00000000000..5eb1d8c5e1b --- /dev/null +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaTest.java @@ -0,0 +1,40 @@ +package org.checkerframework.checker.test.junit.ainferrunners; + +import java.io.File; +import java.util.List; +import org.checkerframework.checker.nullness.NullnessChecker; +import org.checkerframework.framework.test.AinferGeneratePerDirectoryTest; +import org.junit.experimental.categories.Category; +import org.junit.runners.Parameterized.Parameters; + +/** + * Tests whole-program inference with the aid of ajava files. This test is the first pass on the + * test data, which generates the ajava files. This specific test suite is designed to elicit + * problems with ajava parsing that only occur when the Nullness Checker is run. + * + *

IMPORTANT: The errors captured in the tests located in tests/ainfer-index/ are not relevant. + * The meaning of this test class is to test if the generated ajava files are similar to the + * expected ones. The errors on .java files must be ignored. + */ +@Category(AinferNullnessAjavaTest.class) +public class AinferNullnessAjavaTest extends AinferGeneratePerDirectoryTest { + + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferNullnessAjavaTest(List testFiles) { + super( + testFiles, + NullnessChecker.class, + "ainfer-nullness/non-annotated", + "-Anomsgtext", + "-Ainfer=ajava", + // "-Aajava=tests/ainfer-nullness/input-annotation-files/", + "-Awarns"); + } + + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-nullness/non-annotated"}; + } +} diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaValidationTest.java new file mode 100644 index 00000000000..d9e747a3110 --- /dev/null +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaValidationTest.java @@ -0,0 +1,37 @@ +package org.checkerframework.checker.test.junit.ainferrunners; + +import java.io.File; +import java.util.List; +import org.checkerframework.checker.nullness.NullnessChecker; +import org.checkerframework.framework.test.AinferValidatePerDirectoryTest; +import org.junit.experimental.categories.Category; +import org.junit.runners.Parameterized.Parameters; + +/** + * Tests whole-program type inference with ajava files. This test is the second pass, which ensures + * that with the ajava files in place, the errors that those annotations remove are no longer + * issued. + */ +@Category(AinferNullnessAjavaTest.class) +public class AinferNullnessAjavaValidationTest extends AinferValidatePerDirectoryTest { + + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferNullnessAjavaValidationTest(List testFiles) { + super( + testFiles, + NullnessChecker.class, + "nullness", + "ainfer-nullness/annotated", + AinferNullnessAjavaTest.class, + "-Anomsgtext", + ajavaArgFromFiles(testFiles, "nullness"), + "-Awarns"); + } + + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-nullness/annotated/"}; + } +} diff --git a/checker/tests/ainfer-nullness/non-annotated/NullTypeVarTest.java b/checker/tests/ainfer-nullness/non-annotated/NullTypeVarTest.java new file mode 100644 index 00000000000..8c1be238396 --- /dev/null +++ b/checker/tests/ainfer-nullness/non-annotated/NullTypeVarTest.java @@ -0,0 +1,32 @@ +// A test that the interaction between type variables and null types +// is handled correctly in WPI, based on the indentString variable +// in +// https://github.com/plume-lib/bcel-util/blob/master/src/main/java/org/plumelib/bcelutil/SimpleLog.java + +import java.util.List; +import java.util.ArrayList; + +public class NullTypeVarTest { + + // :: warning: assignment + private String indentString = null; + + private List indentStrings; + + private final String INDENT_STR_ONE_LEVEL = " "; + + public NullTypeVarTest() { + indentStrings = new ArrayList(); + indentStrings.add(""); + } + + private String getIndentString(int indentLevel) { + if (indentString == null) { + for (int i = indentStrings.size(); i <= indentLevel; i++) { + indentStrings.add(indentStrings.get(i - 1) + INDENT_STR_ONE_LEVEL); + } + indentString = indentStrings.get(indentLevel); + } + return indentString; + } +} diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java index b2b6432501f..d0631f22fa6 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java @@ -793,26 +793,6 @@ protected void updateAnnotationSet( return; } - // If the rhsATM and the lhsATM have different kinds (which can happen e.g. when - // an array type is substituted for a type parameter), do not attempt to update - // the inferred type, because this method is written with the assumption - // that rhsATM and lhsATM are the same kind. - if (rhsATM.getKind() != lhsATM.getKind()) { - // The one difference in kinds situation that this method can account for is the RHS being - // a literal null expression. - if (!(rhsATM instanceof AnnotatedNullType)) { - if (showWpiFailedInferences) { - printFailedInferenceDebugMessage( - "type structure mismatch, so cannot transfer inferred type" - + "to declared type.\nDeclared type kind: " - + rhsATM.getKind() - + "\nInferred type kind: " - + lhsATM.getKind()); - } - return; - } - } - AnnotatedTypeMirror atmFromStorage = storage.atmFromStorageLocation(rhsATM.getUnderlyingType(), annotationsToUpdate); updateAtmWithLub(rhsATM, atmFromStorage); @@ -859,11 +839,17 @@ private void printFailedInferenceDebugMessage(String reason) { private void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror ajavaATM) { if (sourceCodeATM.getKind() != ajavaATM.getKind()) { - // This can happen e.g. when recursing into the bounds of a type variable: - // the bound on sourceCodeATM might be a declared type (such as T), while - // the ajavaATM might be a typevar (such as S extends T), or vice-versa. In - // that case, use asSuper to make the two ATMs fully-compatible. - sourceCodeATM = AnnotatedTypes.asSuper(this.atypeFactory, sourceCodeATM, ajavaATM); + // Ignore null types: passing them to asSuper causes a crash, as they cannot be + // substituted for type variables. If sourceCodeATM is a null type, only the primary + // annotation will be considered anyway, so there is no danger of recursing into + // typevar bounds. + if (sourceCodeATM.getKind() != TypeKind.NULL) { + // This can happen e.g. when recursing into the bounds of a type variable: + // the bound on sourceCodeATM might be a declared type (such as T), while + // the ajavaATM might be a typevar (such as S extends T), or vice-versa. In + // that case, use asSuper to make the two ATMs fully-compatible. + sourceCodeATM = AnnotatedTypes.asSuper(this.atypeFactory, sourceCodeATM, ajavaATM); + } } switch (sourceCodeATM.getKind()) {