From c7200d83f64bed4be63c7b26a272815cd30802b4 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 6 Oct 2026 08:08:04 -0700 Subject: [PATCH 1/2] Derive each program point's type from its name PptTopLevel.normalize_type derives the type from the name when the declaration gives no type, or gives the type exit or point, so the is_enter, is_exit, is_combined_exit, is_subexit, is_object, and is_class predicates no longer need name-based fallbacks. A numbered exit declared with type exit is a subexit. * Daikon.create_combined_exits does not replace a declared combined exit. * AnnotateNullable handles an .inv file that lacks a combined exit. * PptConditional takes its parent's type. Split from #858. --- java/daikon/AnnotateNullable.java | 111 +++++++++++++++--- java/daikon/Daikon.java | 4 + java/daikon/PptConditional.java | 1 + java/daikon/PptName.java | 18 +++ java/daikon/PptTopLevel.java | 82 ++++++++----- .../KnownValues/KnownValues.txt-daikon.goal | 40 +++---- .../KnownValuesNewDecls.txt-daikon.goal | 4 +- .../NonZeroLargeRange.txt-daikon.goal | 2 +- 8 files changed, 196 insertions(+), 66 deletions(-) diff --git a/java/daikon/AnnotateNullable.java b/java/daikon/AnnotateNullable.java index 5241c5e722..8b0aa62153 100644 --- a/java/daikon/AnnotateNullable.java +++ b/java/daikon/AnnotateNullable.java @@ -1,9 +1,9 @@ package daikon; -import daikon.PptTopLevel.PptType; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -46,6 +46,12 @@ private AnnotateNullable() { /** Map from a class name to the list of static functions for that class. */ static Map> class_map = new LinkedHashMap<>(); + /** + * Map from the name of a combined exit ppt that the .inv file lacks, to the numbered exit ppts of + * that method. An older .inv file may lack a combined exit ppt. + */ + static Map> numbered_exits = new LinkedHashMap<>(); + // The package for the previous class. Used to reduce duplication in // output file. static @Interned String last_package = ""; @@ -93,6 +99,15 @@ public static void main(String[] args) throws IOException { System.out.println(); } + for (PptTopLevel ppt : ppts.pptIterable()) { + if (ppt.is_subexit()) { + PptName exit_name = ppt.ppt_name.makeExit(); + if (ppts.get(exit_name) == null) { + numbered_exits.computeIfAbsent(exit_name, k -> new ArrayList<>()).add(ppt); + } + } + } + // Find all exit ppts that do not have a parent and determine what // class they are associated with. These are static methods for classes // without any static variables (no class ppt is created if there are no @@ -112,7 +127,7 @@ public static void main(String[] args) throws IOException { // static method can be identified because it will not have the OBJECT // point as a parent. for (PptTopLevel ppt : ppts.pptIterable()) { - if (!ppt.is_combined_exit() || !is_static_method(ppt)) { + if (!is_method_exit(ppt) || !is_static_method(ppt)) { continue; } @@ -149,8 +164,8 @@ public static void main(String[] args) throws IOException { for (int i = 0; i < ppt.children.size(); i++) { PptRelation child_rel = ppt.children.get(i); PptTopLevel child = child_rel.child; - // Skip enter ppts, all of the info is at the exit. - if ((child.type == PptType.ENTER) || (child.type == PptType.OBJECT)) { + // Skip enter ppts and others; all the info is at the exit ppt that represents the method. + if (!is_method_exit(child)) { continue; } child_cnt++; @@ -236,8 +251,8 @@ public static void process_class(PptTopLevel object_ppt) { if (class_ppt != null) { for (PptRelation child_rel : class_ppt.children) { PptTopLevel child = child_rel.child; - // Skip enter ppts, all of the info is at the exit. - if ((child.type == PptType.ENTER) || (child.type == PptType.OBJECT)) { + // Skip enter ppts and others; all the info is at the exit ppt that represents the method. + if (!is_method_exit(child)) { continue; } // debug.log("processing static method %s, type %s", child, child.type); @@ -257,8 +272,8 @@ public static void process_class(PptTopLevel object_ppt) { // Process member (non-static) methods for (PptRelation child_rel : object_ppt.children) { PptTopLevel child = child_rel.child; - // Skip enter ppts, all of the info is at the exit. - if (child.type == PptType.ENTER) { + // Skip enter ppts and others; all the info is at the exit ppt that represents the method. + if (!is_method_exit(child)) { continue; } // debug.log("processing method %s, type %s", child, child.type); @@ -278,14 +293,34 @@ public static void process_class(PptTopLevel object_ppt) { * string if no annotation is applicable. Otherwise, the return value contains a trailing space. */ public static String get_annotation(PptTopLevel ppt, VarInfo vi) { + return get_annotation(Collections.singletonList(ppt), vi); + } + + /** + * Returns the annotation for the specified variable, considering all the given exit ppts of a + * method. Returns @Nullable if, at any of the ppts, samples were found for the variable and at + * least one sample contained a null value. Otherwise behaves like {@link + * #get_annotation(PptTopLevel, VarInfo)}. + * + * @param exits the exit ppts of a method, which all declare a variable named like {@code vi} + * @param vi a variable of the first of the exit ppts + * @return the annotation for the variable + */ + public static String get_annotation(List exits, VarInfo vi) { if (vi.type.isPrimitive()) { return ""; } String annotation = (nonnull_annotations ? "NonNull" : ""); - if ((ppt.num_samples(vi) > 0) && !ppt.is_nonzero(vi)) { - annotation = "Nullable"; + for (PptTopLevel ppt : exits) { + VarInfo ppt_vi = ppt.find_var_by_name(vi.name()); + if (ppt_vi == null) { + continue; + } + if ((ppt.num_samples(ppt_vi) > 0) && !ppt.is_nonzero(ppt_vi)) { + annotation = "Nullable"; + } } if (annotation != "") { // interned // if (! stub_format) { @@ -299,7 +334,12 @@ public static String get_annotation(PptTopLevel ppt, VarInfo vi) { /** Print out the annotations for the specified method. */ public static void process_method(PptTopLevel ppt) { - assert ppt.type == PptType.EXIT : ppt; + assert is_method_exit(ppt) : ppt; + List exits = method_exits(ppt); + int num_samples = 0; + for (PptTopLevel exit : exits) { + num_samples += exit.num_samples(); + } // Get all of the parameters to the method and the return value List params = new ArrayList<>(); @@ -317,7 +357,7 @@ public static void process_method(PptTopLevel ppt) { } // The formatted annotation for the return value with a leading space, or empty string - String return_annotation = (retvar == null ? "" : " " + get_annotation(ppt, retvar)); + String return_annotation = (retvar == null ? "" : " " + get_annotation(exits, retvar)); // Look up the annotation for each parameter. List names = new ArrayList<>(); @@ -326,7 +366,7 @@ public static void process_method(PptTopLevel ppt) { String annotation = ""; names.add(param.name()); if (param.file_rep_type.isHashcode()) { - annotation = get_annotation(ppt, param); + annotation = get_annotation(exits, param); } annos.add(annotation); } @@ -339,9 +379,9 @@ public static void process_method(PptTopLevel ppt) { sj.add(String.format("%s %s %s", annos.get(i), "type-goes-here", names.get(i))); } System.out.printf("%s", sj); - System.out.printf("; // %d samples%n", ppt.num_samples()); + System.out.printf("; // %d samples%n", num_samples); } else { - System.out.printf(" method %s : // %d samples%n", jvm_signature(ppt), ppt.num_samples()); + System.out.printf(" method %s : // %d samples%n", jvm_signature(ppt), num_samples); System.out.printf(" return:%s%n", return_annotation); for (int i = 0; i < params.size(); i++) { // Print the annotation for this parameter @@ -439,6 +479,47 @@ public static String field_name(VarInfo vi) { } } + /** + * Returns true if the ppt is the exit ppt that represents its method. That is usually the + * combined exit ppt. However, an older .inv file may lack a combined exit ppt, in which case the + * numbered exit ppts are direct children of the OBJECT or CLASS ppt, and the first of them + * represents the method. + * + * @param ppt a program point + * @return true if the ppt is the exit ppt that represents its method + * @see #method_exits + */ + @Pure + public static boolean is_method_exit(PptTopLevel ppt) { + if (ppt.is_combined_exit()) { + return true; + } + if (!ppt.is_subexit()) { + return false; + } + List exits = numbered_exits.get(ppt.ppt_name.makeExit()); + return exits != null && exits.get(0) == ppt; + } + + /** + * Returns the exit ppts that together summarize all exits from the method that the given ppt + * represents: the combined exit ppt, or all the numbered exit ppts if there is no combined exit + * ppt. + * + * @param ppt the exit ppt that represents its method + * @return the exit ppts that together summarize all exits from the method + * @see #is_method_exit + */ + private static List method_exits(PptTopLevel ppt) { + assert is_method_exit(ppt) : ppt; + if (ppt.is_combined_exit()) { + return Collections.singletonList(ppt); + } + @SuppressWarnings("nullness:assignment") // is_method_exit(ppt) guarantees the map has the key + @NonNull List exits = numbered_exits.get(ppt.ppt_name.makeExit()); + return exits; + } + /** * Returns true if the method of the specified ppt is static or not. The ppt must be an exit ppt. * Exit ppts that do not have an object as a parent are inferred to be static. This does not work diff --git a/java/daikon/Daikon.java b/java/daikon/Daikon.java index 1e7c1e1b73..5b38b6a8bc 100644 --- a/java/daikon/Daikon.java +++ b/java/daikon/Daikon.java @@ -1876,6 +1876,10 @@ public static void create_combined_exits(PptMap ppts) { PptTopLevel exitnn_ppt = ppt; PptName exit_name = ppt.ppt_name.makeExit(); + // Don't replace a combined exit point that the declarations already contain. + if (ppts.get(exit_name) != null) { + continue; + } PptTopLevel exit_ppt = exit_ppts.get(exit_name); if (debugInit.isLoggable(FINE)) { diff --git a/java/daikon/PptConditional.java b/java/daikon/PptConditional.java index 45aa21f899..867064c652 100644 --- a/java/daikon/PptConditional.java +++ b/java/daikon/PptConditional.java @@ -29,6 +29,7 @@ public PptConditional(PptTopLevel parent, Splitter splitter, boolean splitter_in super(ctor_name_helper(parent, splitter, splitter_inverse), ctor_vis_helper(parent)); // assert splitter.instantiated() == false; this.parent = parent; + this.type = parent.type; @SuppressWarnings({"nullness"}) // won't be used until it's fully initialized @Initialized PptConditional thisNonRaw = this; this.splitter = splitter.instantiateSplitter(thisNonRaw); diff --git a/java/daikon/PptName.java b/java/daikon/PptName.java index e585757477..21636b01a0 100644 --- a/java/daikon/PptName.java +++ b/java/daikon/PptName.java @@ -359,6 +359,23 @@ public boolean isNumberedExitPoint() { return (point != null) && (isExitPoint() && !isCombinedExitPoint()); } + /** + * Returns true iff this name is that of a numbered exit point: it ends with ":::EXIT" followed by + * one or more digits, such as foo:::EXIT22. By contrast, {@link #isNumberedExitPoint} is also + * true for names such as foo:::EXIT_CONDITION. + * + * @return true iff this name is that of a numbered exit point + */ + @EnsuresNonNullIf(result = true, expression = "point") + @Pure + public boolean isExitWithLineNumber() { + if (!isExitPoint()) { + return false; + } + String line = exitLine(); + return !line.isEmpty() && point.length() == FileIO.exit_suffix.length() + line.length(); + } + /** * Returns true iff this name refers to a procedure exit point. * @@ -378,6 +395,7 @@ public boolean isEnterPoint() { * empty string * @see #getPointSubscript() */ + @SideEffectFree public String exitLine() { if (!isExitPoint()) { return ""; diff --git a/java/daikon/PptTopLevel.java b/java/daikon/PptTopLevel.java index e2faebd540..4b44267a12 100644 --- a/java/daikon/PptTopLevel.java +++ b/java/daikon/PptTopLevel.java @@ -423,7 +423,7 @@ public PptTopLevel( } this.ppt_name = new PptName(name); this.flags = flags; - this.type = type; + this.type = normalize_type(ppt_name, type); this.parent_relations = parents; init_vars(); } @@ -431,6 +431,52 @@ public PptTopLevel( /** Restore/Create interns when reading serialized object. */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); + type = normalize_type(ppt_name, type); + } + + /** + * Returns the type that a program point with the given name and declared type should have. A + * program point that has no declared type (as in a version 1 decls file), or that is declared + * with type {@link PptType#EXIT} or with the default type {@link PptType#POINT}, gets its type + * from its name: + * + *
    + *
  • foo:::ENTER is an {@link PptType#ENTER}, + *
  • a combined exit point such as foo:::EXIT is an {@link PptType#EXIT}, + *
  • a numbered exit point such as foo:::EXIT22 is a {@link PptType#SUBEXIT}, + *
  • Foo:::OBJECT is an {@link PptType#OBJECT}, + *
  • Foo:::CLASS is a {@link PptType#CLASS}, and + *
  • any other program point is a generic {@link PptType#POINT}. + *
+ * + * (Some front ends declare a numbered exit with type {@link PptType#EXIT}, and a version 2 decls + * file may omit the type.) As a result, the predicates is_subexit, is_combined_exit, etc. agree + * with one another and with the program point's name. + * + * @param ppt_name the name of the program point + * @param type the declared type of the program point, or null if none was declared + * @return the type that the program point should have + */ + private static PptType normalize_type(PptName ppt_name, @Nullable PptType type) { + if (type != null && type != PptType.EXIT && type != PptType.POINT) { + return type; + } + if (ppt_name.isEnterPoint()) { + return PptType.ENTER; + } + if (ppt_name.isCombinedExitPoint()) { + return PptType.EXIT; + } + if (ppt_name.isExitWithLineNumber()) { + return PptType.SUBEXIT; + } + if (ppt_name.isObjectInstanceSynthetic()) { + return PptType.OBJECT; + } + if (ppt_name.isClassStaticSynthetic()) { + return PptType.CLASS; + } + return PptType.POINT; } // Used by DaikonSimple, InvMap, and tests. Violates invariants. @@ -440,6 +486,7 @@ public PptTopLevel(String name, VarInfo[] var_infos) { super(var_infos); this.name = name; ppt_name = new PptName(name); + type = normalize_type(ppt_name, null); init_vars(); } @@ -4646,11 +4693,7 @@ public void incSampleNumber() { /** Is this is an exit ppt (combined or specific)? */ @Pure public boolean is_exit() { - if (type != null) { - return (type == PptType.EXIT) || (type == PptType.SUBEXIT); - } else { - return ppt_name.isExitPoint(); - } + return (type == PptType.EXIT) || (type == PptType.SUBEXIT); } /** @@ -4660,48 +4703,31 @@ public boolean is_exit() { */ @Pure public boolean is_enter() { - if (type != null) { - return (type == PptType.ENTER); - } else { - return ppt_name.isEnterPoint(); - } + return type == PptType.ENTER; } /** Is this a combined exit point? */ @Pure public boolean is_combined_exit() { - if (type != null) { - return (type == PptType.EXIT); - } else { - return ppt_name.isCombinedExitPoint(); - } + return type == PptType.EXIT; } /** Is this a numbered (specific) exit point? */ @Pure public boolean is_subexit() { - if (type != null) { - return (type == PptType.SUBEXIT); - } else { - return ppt_name.isExitPoint() && !ppt_name.isCombinedExitPoint(); - } + return type == PptType.SUBEXIT; } /** Is this a ppt that represents an object? */ @Pure public boolean is_object() { - if (type != null) { - return (type == PptType.OBJECT); - } else { - return ppt_name.isObjectInstanceSynthetic(); - } + return type == PptType.OBJECT; } /** Is this a ppt that represents a class? */ - @EnsuresNonNullIf(result = true, expression = "type") @Pure public boolean is_class() { - return (type != null && type == PptType.CLASS); + return type == PptType.CLASS; } public String var_names() { diff --git a/tests/daikon-tests/KnownValues/KnownValues.txt-daikon.goal b/tests/daikon-tests/KnownValues/KnownValues.txt-daikon.goal index 53a25bb68a..3357b28b04 100644 --- a/tests/daikon-tests/KnownValues/KnownValues.txt-daikon.goal +++ b/tests/daikon-tests/KnownValues/KnownValues.txt-daikon.goal @@ -1,59 +1,59 @@ =========================================================================== -constantMinMaxDifferent:::EXIT1 +constantMinMaxDifferent:::EXIT return == 1 =========================================================================== -constantMinMaxMatch:::EXIT1 +constantMinMaxMatch:::EXIT =========================================================================== -constantPairMinMaxMatch:::EXIT1 +constantPairMinMaxMatch:::EXIT =========================================================================== -intOneOfNotSuppressed:::EXIT1 +intOneOfNotSuppressed:::EXIT a one of { 0, 1, 2 } =========================================================================== -intOneOfSuppressed:::EXIT1 +intOneOfSuppressed:::EXIT =========================================================================== -lowerBoundNotSuppressedStronger:::EXIT1 +lowerBoundNotSuppressedStronger:::EXIT a >= 1 =========================================================================== -lowerBoundNotSuppressedWeaker:::EXIT1 +lowerBoundNotSuppressedWeaker:::EXIT a >= 1 =========================================================================== -lowerBoundSuppressed:::EXIT1 +lowerBoundSuppressed:::EXIT =========================================================================== -sequenceElementOneOfSuppressed:::EXIT1 +sequenceElementOneOfSuppressed:::EXIT size(a[]) == 7 =========================================================================== -sequenceIntOneOfNotSuppressed:::EXIT1 +sequenceIntOneOfNotSuppressed:::EXIT a[] == [0, 1, 0, 2] a[] elements one of { 0, 1, 2 } size(a[]) == 4 =========================================================================== -sequenceIntOneOfSuppressed:::EXIT1 +sequenceIntOneOfSuppressed:::EXIT a[] == [0, 1, 0] size(a[]) == 3 =========================================================================== -sequenceOneOfSuppressed:::EXIT1 +sequenceOneOfSuppressed:::EXIT =========================================================================== -sequenceStringValidValuesNotSuppressed:::EXIT1 +sequenceStringValidValuesNotSuppressed:::EXIT a[] == [true, false, potato] a[] elements one of { "false", "potato", "true" } size(a[]) == 3 =========================================================================== -sequenceStringValidValuesSuppressed:::EXIT1 +sequenceStringValidValuesSuppressed:::EXIT a[] == [true, false] size(a[]) == 2 =========================================================================== -singletonSequenceOneOfSuppressed:::EXIT1 +singletonSequenceOneOfSuppressed:::EXIT a has only one value =========================================================================== -stringValidValuesNotSuppressed:::EXIT1 +stringValidValuesNotSuppressed:::EXIT a one of { "false", "potato", "true" } a is printable =========================================================================== -stringValidValuesSuppressed:::EXIT1 +stringValidValuesSuppressed:::EXIT a is printable =========================================================================== -upperBoundSuppressed:::EXIT1 +upperBoundSuppressed:::EXIT =========================================================================== -variableGreaterEqualSuppressed:::EXIT1 +variableGreaterEqualSuppressed:::EXIT =========================================================================== -variableGreaterSuppressed:::EXIT1 +variableGreaterSuppressed:::EXIT diff --git a/tests/daikon-tests/KnownValuesNewDecls/KnownValuesNewDecls.txt-daikon.goal b/tests/daikon-tests/KnownValuesNewDecls/KnownValuesNewDecls.txt-daikon.goal index 0caa35c865..2c0982e51e 100644 --- a/tests/daikon-tests/KnownValuesNewDecls/KnownValuesNewDecls.txt-daikon.goal +++ b/tests/daikon-tests/KnownValuesNewDecls/KnownValuesNewDecls.txt-daikon.goal @@ -1,5 +1,5 @@ =========================================================================== -constantMinMaxMatch:::EXIT1 +constantMinMaxMatch:::EXIT =========================================================================== -singletonSequenceOneOfSuppressed:::EXIT1 +singletonSequenceOneOfSuppressed:::EXIT a has only one value diff --git a/tests/daikon-tests/NonZeroLargeRange/NonZeroLargeRange.txt-daikon.goal b/tests/daikon-tests/NonZeroLargeRange/NonZeroLargeRange.txt-daikon.goal index b00612fedc..1b1c105b93 100644 --- a/tests/daikon-tests/NonZeroLargeRange/NonZeroLargeRange.txt-daikon.goal +++ b/tests/daikon-tests/NonZeroLargeRange/NonZeroLargeRange.txt-daikon.goal @@ -3,7 +3,7 @@ NonZeroLargeRange.m(long):::ENTER x != 0 x is a pointer =========================================================================== -NonZeroLargeRange.m(long):::EXIT1 +NonZeroLargeRange.m(long):::EXIT x == orig(x) x != 0 x is a pointer From 64521297955de79d244951a96b24730c23ca256d Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 6 Oct 2026 09:12:21 -0700 Subject: [PATCH 2/2] Validate declared program point types against names * A missing ppt-type is derived from the name; an explicit "point" is kept. * A ppt declared exit or subexit is a combined or numbered exit according to its name; any other declared type must conform to the name, or reading the declaration fails with an error. * Remove backward-compatibility code for older .inv files. * Daikon.create_combined_exits again replaces a declared combined exit. * Remove PptName.isExitWithLineNumber; use isNumberedExitPoint. --- java/daikon/AnnotateNullable.java | 111 ++++-------------------------- java/daikon/Daikon.java | 4 -- java/daikon/FileIO.java | 12 ++-- java/daikon/PptName.java | 18 ----- java/daikon/PptTopLevel.java | 90 ++++++++++++++++-------- 5 files changed, 86 insertions(+), 149 deletions(-) diff --git a/java/daikon/AnnotateNullable.java b/java/daikon/AnnotateNullable.java index 8b0aa62153..5241c5e722 100644 --- a/java/daikon/AnnotateNullable.java +++ b/java/daikon/AnnotateNullable.java @@ -1,9 +1,9 @@ package daikon; +import daikon.PptTopLevel.PptType; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -46,12 +46,6 @@ private AnnotateNullable() { /** Map from a class name to the list of static functions for that class. */ static Map> class_map = new LinkedHashMap<>(); - /** - * Map from the name of a combined exit ppt that the .inv file lacks, to the numbered exit ppts of - * that method. An older .inv file may lack a combined exit ppt. - */ - static Map> numbered_exits = new LinkedHashMap<>(); - // The package for the previous class. Used to reduce duplication in // output file. static @Interned String last_package = ""; @@ -99,15 +93,6 @@ public static void main(String[] args) throws IOException { System.out.println(); } - for (PptTopLevel ppt : ppts.pptIterable()) { - if (ppt.is_subexit()) { - PptName exit_name = ppt.ppt_name.makeExit(); - if (ppts.get(exit_name) == null) { - numbered_exits.computeIfAbsent(exit_name, k -> new ArrayList<>()).add(ppt); - } - } - } - // Find all exit ppts that do not have a parent and determine what // class they are associated with. These are static methods for classes // without any static variables (no class ppt is created if there are no @@ -127,7 +112,7 @@ public static void main(String[] args) throws IOException { // static method can be identified because it will not have the OBJECT // point as a parent. for (PptTopLevel ppt : ppts.pptIterable()) { - if (!is_method_exit(ppt) || !is_static_method(ppt)) { + if (!ppt.is_combined_exit() || !is_static_method(ppt)) { continue; } @@ -164,8 +149,8 @@ public static void main(String[] args) throws IOException { for (int i = 0; i < ppt.children.size(); i++) { PptRelation child_rel = ppt.children.get(i); PptTopLevel child = child_rel.child; - // Skip enter ppts and others; all the info is at the exit ppt that represents the method. - if (!is_method_exit(child)) { + // Skip enter ppts, all of the info is at the exit. + if ((child.type == PptType.ENTER) || (child.type == PptType.OBJECT)) { continue; } child_cnt++; @@ -251,8 +236,8 @@ public static void process_class(PptTopLevel object_ppt) { if (class_ppt != null) { for (PptRelation child_rel : class_ppt.children) { PptTopLevel child = child_rel.child; - // Skip enter ppts and others; all the info is at the exit ppt that represents the method. - if (!is_method_exit(child)) { + // Skip enter ppts, all of the info is at the exit. + if ((child.type == PptType.ENTER) || (child.type == PptType.OBJECT)) { continue; } // debug.log("processing static method %s, type %s", child, child.type); @@ -272,8 +257,8 @@ public static void process_class(PptTopLevel object_ppt) { // Process member (non-static) methods for (PptRelation child_rel : object_ppt.children) { PptTopLevel child = child_rel.child; - // Skip enter ppts and others; all the info is at the exit ppt that represents the method. - if (!is_method_exit(child)) { + // Skip enter ppts, all of the info is at the exit. + if (child.type == PptType.ENTER) { continue; } // debug.log("processing method %s, type %s", child, child.type); @@ -293,34 +278,14 @@ public static void process_class(PptTopLevel object_ppt) { * string if no annotation is applicable. Otherwise, the return value contains a trailing space. */ public static String get_annotation(PptTopLevel ppt, VarInfo vi) { - return get_annotation(Collections.singletonList(ppt), vi); - } - - /** - * Returns the annotation for the specified variable, considering all the given exit ppts of a - * method. Returns @Nullable if, at any of the ppts, samples were found for the variable and at - * least one sample contained a null value. Otherwise behaves like {@link - * #get_annotation(PptTopLevel, VarInfo)}. - * - * @param exits the exit ppts of a method, which all declare a variable named like {@code vi} - * @param vi a variable of the first of the exit ppts - * @return the annotation for the variable - */ - public static String get_annotation(List exits, VarInfo vi) { if (vi.type.isPrimitive()) { return ""; } String annotation = (nonnull_annotations ? "NonNull" : ""); - for (PptTopLevel ppt : exits) { - VarInfo ppt_vi = ppt.find_var_by_name(vi.name()); - if (ppt_vi == null) { - continue; - } - if ((ppt.num_samples(ppt_vi) > 0) && !ppt.is_nonzero(ppt_vi)) { - annotation = "Nullable"; - } + if ((ppt.num_samples(vi) > 0) && !ppt.is_nonzero(vi)) { + annotation = "Nullable"; } if (annotation != "") { // interned // if (! stub_format) { @@ -334,12 +299,7 @@ public static String get_annotation(List exits, VarInfo vi) { /** Print out the annotations for the specified method. */ public static void process_method(PptTopLevel ppt) { - assert is_method_exit(ppt) : ppt; - List exits = method_exits(ppt); - int num_samples = 0; - for (PptTopLevel exit : exits) { - num_samples += exit.num_samples(); - } + assert ppt.type == PptType.EXIT : ppt; // Get all of the parameters to the method and the return value List params = new ArrayList<>(); @@ -357,7 +317,7 @@ public static void process_method(PptTopLevel ppt) { } // The formatted annotation for the return value with a leading space, or empty string - String return_annotation = (retvar == null ? "" : " " + get_annotation(exits, retvar)); + String return_annotation = (retvar == null ? "" : " " + get_annotation(ppt, retvar)); // Look up the annotation for each parameter. List names = new ArrayList<>(); @@ -366,7 +326,7 @@ public static void process_method(PptTopLevel ppt) { String annotation = ""; names.add(param.name()); if (param.file_rep_type.isHashcode()) { - annotation = get_annotation(exits, param); + annotation = get_annotation(ppt, param); } annos.add(annotation); } @@ -379,9 +339,9 @@ public static void process_method(PptTopLevel ppt) { sj.add(String.format("%s %s %s", annos.get(i), "type-goes-here", names.get(i))); } System.out.printf("%s", sj); - System.out.printf("; // %d samples%n", num_samples); + System.out.printf("; // %d samples%n", ppt.num_samples()); } else { - System.out.printf(" method %s : // %d samples%n", jvm_signature(ppt), num_samples); + System.out.printf(" method %s : // %d samples%n", jvm_signature(ppt), ppt.num_samples()); System.out.printf(" return:%s%n", return_annotation); for (int i = 0; i < params.size(); i++) { // Print the annotation for this parameter @@ -479,47 +439,6 @@ public static String field_name(VarInfo vi) { } } - /** - * Returns true if the ppt is the exit ppt that represents its method. That is usually the - * combined exit ppt. However, an older .inv file may lack a combined exit ppt, in which case the - * numbered exit ppts are direct children of the OBJECT or CLASS ppt, and the first of them - * represents the method. - * - * @param ppt a program point - * @return true if the ppt is the exit ppt that represents its method - * @see #method_exits - */ - @Pure - public static boolean is_method_exit(PptTopLevel ppt) { - if (ppt.is_combined_exit()) { - return true; - } - if (!ppt.is_subexit()) { - return false; - } - List exits = numbered_exits.get(ppt.ppt_name.makeExit()); - return exits != null && exits.get(0) == ppt; - } - - /** - * Returns the exit ppts that together summarize all exits from the method that the given ppt - * represents: the combined exit ppt, or all the numbered exit ppts if there is no combined exit - * ppt. - * - * @param ppt the exit ppt that represents its method - * @return the exit ppts that together summarize all exits from the method - * @see #is_method_exit - */ - private static List method_exits(PptTopLevel ppt) { - assert is_method_exit(ppt) : ppt; - if (ppt.is_combined_exit()) { - return Collections.singletonList(ppt); - } - @SuppressWarnings("nullness:assignment") // is_method_exit(ppt) guarantees the map has the key - @NonNull List exits = numbered_exits.get(ppt.ppt_name.makeExit()); - return exits; - } - /** * Returns true if the method of the specified ppt is static or not. The ppt must be an exit ppt. * Exit ppts that do not have an object as a parent are inferred to be static. This does not work diff --git a/java/daikon/Daikon.java b/java/daikon/Daikon.java index 5b38b6a8bc..1e7c1e1b73 100644 --- a/java/daikon/Daikon.java +++ b/java/daikon/Daikon.java @@ -1876,10 +1876,6 @@ public static void create_combined_exits(PptMap ppts) { PptTopLevel exitnn_ppt = ppt; PptName exit_name = ppt.ppt_name.makeExit(); - // Don't replace a combined exit point that the declarations already contain. - if (ppts.get(exit_name) != null) { - continue; - } PptTopLevel exit_ppt = exit_ppts.get(exit_name); if (debugInit.isLoggable(FINE)) { diff --git a/java/daikon/FileIO.java b/java/daikon/FileIO.java index 92873ee121..0790b41f2c 100644 --- a/java/daikon/FileIO.java +++ b/java/daikon/FileIO.java @@ -376,7 +376,8 @@ public static void read_declaration_file(File filename, PptMap all_ppts) throws VarDefinition vardef = null; List ppt_parents = new ArrayList<>(); EnumSet ppt_flags = EnumSet.noneOf(PptFlags.class); - PptType ppt_type = PptType.POINT; + // Null if the declaration has no ppt-type record. + PptType ppt_type = null; try { // Read the records that define this program point @@ -504,9 +505,12 @@ public static void read_declaration_file(File filename, PptMap all_ppts) throws } // Build the program point - PptTopLevel newppt = new PptTopLevel(ppt_name, ppt_type, ppt_parents, ppt_flags, vi_array); - - return newppt; + try { + return new PptTopLevel(ppt_name, ppt_type, ppt_parents, ppt_flags, vi_array); + } catch (IllegalArgumentException e) { + decl_error(state, e); + throw new Error(); // this can't happen + } } /** Parses a ppt parent hierarchy record and returns it. */ diff --git a/java/daikon/PptName.java b/java/daikon/PptName.java index 21636b01a0..e585757477 100644 --- a/java/daikon/PptName.java +++ b/java/daikon/PptName.java @@ -359,23 +359,6 @@ public boolean isNumberedExitPoint() { return (point != null) && (isExitPoint() && !isCombinedExitPoint()); } - /** - * Returns true iff this name is that of a numbered exit point: it ends with ":::EXIT" followed by - * one or more digits, such as foo:::EXIT22. By contrast, {@link #isNumberedExitPoint} is also - * true for names such as foo:::EXIT_CONDITION. - * - * @return true iff this name is that of a numbered exit point - */ - @EnsuresNonNullIf(result = true, expression = "point") - @Pure - public boolean isExitWithLineNumber() { - if (!isExitPoint()) { - return false; - } - String line = exitLine(); - return !line.isEmpty() && point.length() == FileIO.exit_suffix.length() + line.length(); - } - /** * Returns true iff this name refers to a procedure exit point. * @@ -395,7 +378,6 @@ public boolean isEnterPoint() { * empty string * @see #getPointSubscript() */ - @SideEffectFree public String exitLine() { if (!isExitPoint()) { return ""; diff --git a/java/daikon/PptTopLevel.java b/java/daikon/PptTopLevel.java index 4b44267a12..b120e4c0bd 100644 --- a/java/daikon/PptTopLevel.java +++ b/java/daikon/PptTopLevel.java @@ -89,6 +89,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; @@ -411,7 +412,7 @@ public boolean has_splitters() { @SuppressWarnings("fields.uninitialized") // todo: initialization and helper methods public PptTopLevel( String name, - PptType type, + @Nullable PptType type, List parents, EnumSet flags, VarInfo[] var_infos) { @@ -419,7 +420,7 @@ public PptTopLevel( this.name = name; if (!name.contains(":::")) { - name += ":::" + type; + name += ":::" + (type == null ? PptType.POINT : type); } this.ppt_name = new PptName(name); this.flags = flags; @@ -431,52 +432,87 @@ public PptTopLevel( /** Restore/Create interns when reading serialized object. */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); - type = normalize_type(ppt_name, type); } /** - * Returns the type that a program point with the given name and declared type should have. A - * program point that has no declared type (as in a version 1 decls file), or that is declared - * with type {@link PptType#EXIT} or with the default type {@link PptType#POINT}, gets its type - * from its name: + * Returns the type that a program point with the given name and declared type should have. + * + *

A program point that has no declared type (as in a version 1 decls file, or a version 2 + * decls file that omits the ppt-type record) gets its type from its name: * *

    *
  • foo:::ENTER is an {@link PptType#ENTER}, - *
  • a combined exit point such as foo:::EXIT is an {@link PptType#EXIT}, + *
  • a combined exit point, foo:::EXIT, is an {@link PptType#EXIT}, *
  • a numbered exit point such as foo:::EXIT22 is a {@link PptType#SUBEXIT}, *
  • Foo:::OBJECT is an {@link PptType#OBJECT}, *
  • Foo:::CLASS is a {@link PptType#CLASS}, and *
  • any other program point is a generic {@link PptType#POINT}. *
* - * (Some front ends declare a numbered exit with type {@link PptType#EXIT}, and a version 2 decls - * file may omit the type.) As a result, the predicates is_subexit, is_combined_exit, etc. agree - * with one another and with the program point's name. + *

A program point that is declared with type {@link PptType#EXIT} or {@link PptType#SUBEXIT} + * is a combined exit or a numbered exit, according to its name. (Some front ends declare a + * numbered exit with type {@link PptType#EXIT}.) + * + *

A program point that is declared with any other type keeps that type. Its name must conform + * to the type, except for {@link PptType#POINT}, which permits any name. + * + *

As a result, the predicates is_enter, is_subexit, is_combined_exit, etc. agree with one + * another and with the program point's name. * * @param ppt_name the name of the program point * @param type the declared type of the program point, or null if none was declared * @return the type that the program point should have + * @throws IllegalArgumentException if the declared type does not conform to the name */ private static PptType normalize_type(PptName ppt_name, @Nullable PptType type) { - if (type != null && type != PptType.EXIT && type != PptType.POINT) { - return type; - } - if (ppt_name.isEnterPoint()) { - return PptType.ENTER; - } - if (ppt_name.isCombinedExitPoint()) { - return PptType.EXIT; - } - if (ppt_name.isExitWithLineNumber()) { - return PptType.SUBEXIT; + if (type == null) { + if (ppt_name.isEnterPoint()) { + return PptType.ENTER; + } else if (ppt_name.isCombinedExitPoint()) { + return PptType.EXIT; + } else if (ppt_name.isNumberedExitPoint()) { + return PptType.SUBEXIT; + } else if (ppt_name.isObjectInstanceSynthetic()) { + return PptType.OBJECT; + } else if (ppt_name.isClassStaticSynthetic()) { + return PptType.CLASS; + } else { + return PptType.POINT; + } } - if (ppt_name.isObjectInstanceSynthetic()) { - return PptType.OBJECT; + + boolean conforms; + switch (type) { + case POINT: + return type; + case ENTER: + conforms = ppt_name.isEnterPoint(); + break; + case EXIT: + case SUBEXIT: + if (ppt_name.isCombinedExitPoint()) { + return PptType.EXIT; + } else if (ppt_name.isNumberedExitPoint()) { + return PptType.SUBEXIT; + } + conforms = false; + break; + case OBJECT: + conforms = ppt_name.isObjectInstanceSynthetic(); + break; + case CLASS: + conforms = ppt_name.isClassStaticSynthetic(); + break; + default: + throw new Error("Unexpected program point type " + type); } - if (ppt_name.isClassStaticSynthetic()) { - return PptType.CLASS; + if (!conforms) { + throw new IllegalArgumentException( + String.format( + "Program point %s has type %s, but its name does not conform to that type", + ppt_name.getName(), type.toString().toLowerCase(Locale.ROOT))); } - return PptType.POINT; + return type; } // Used by DaikonSimple, InvMap, and tests. Violates invariants.