diff --git a/graph/java/engine/resolution/external-types.dl b/graph/java/engine/resolution/external-types.dl index e8a88fb13..c60de8a6b 100644 --- a/graph/java/engine/resolution/external-types.dl +++ b/graph/java/engine/resolution/external-types.dl @@ -73,10 +73,16 @@ simple_name_declared(n) :- lib_type(n, _, _, _, _, _, _, _, _, _, _, _, _, _). name_resolves_in_file(tn, fp) :- type_resolves_in_file(_, tn, fp, _). name_resolves_in_file(tn, fp) :- ref_file(_, tn, fp), type_resolves_java_lang(_, tn, _). +// `var` is not a type name (JLS 3.9, 14.4). The parser records `var h = r.lookup(k)` as a +// LOCAL_VARIABLE reference written `var` that nothing resolves, so without this guard every call +// on an inferred local got an `external:var.` boundary beside its real target (3,842 of 85,025 +// edges on a 1,360-file project). Its type is the initializer's: local-flow.dl carries a client +// one, and the `var` clause of expr_type below an external one. #1401. candidate_ref(ref, tn, ctn, fp) :- java_type_reference(kind, ctx, _, _, _, _, _, "0", tn, ctn, _, _, _, _, _, _, _, ref), ext_ref_kind(kind), ext_ref_context(ctx), tn != "", + tn != "var", ref_file(ref, tn, fp), !name_resolves_in_file(tn, fp), !simple_name_declared(tn). @@ -149,6 +155,16 @@ expr_type("external", e, x) :- java_expression(_, _, _, _, _, _, _, _, _, _, nam java_local_variable(name, _, _, _, _, _, _, _, _, _, _, _, _, m, _, _, _, _, local), !local_creation_pinned(local), local_var_type_external(local, x). +// A `var` local's static type IS its initializer's (JLS 14.4.1), fixed at the declaration, so a +// reassignment does not widen it. When that initializer is typed external (`var f = source()` +// with `Format source()` unstaged), the local is that external type exactly as `Format f` would +// be. Without it the receiver has no external type at all and the call is a declared unknown. +// java_local_variable (19): 11 isVarInferred 13 method 18 hash +expr_type("external", e, x) :- java_expression(_, _, _, _, _, _, _, _, _, _, name, _, _, _, "LOCAL_VARIABLE", _, _, _, _, _, _, _, _, _, e), + expr_ultimate_method("client", e, m), + java_local_variable(name, _, _, _, _, _, _, _, _, _, _, "true", _, m, _, _, _, _, local), + local_init_expr(local, init), + expr_type("external", init, x). expr_type("external", e, x) :- java_expression("CAST_EXPRESSION", _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, e), cast_type_external(e, x). // A chained receiver whose callee is a client method declared to return the external type. diff --git a/graph/java/engine/resolution/reference-types.dl b/graph/java/engine/resolution/reference-types.dl index 92d9bdb60..702199541 100644 --- a/graph/java/engine/resolution/reference-types.dl +++ b/graph/java/engine/resolution/reference-types.dl @@ -39,7 +39,8 @@ field_type_resolves(prov, field, ref, t) :- java_type_reference(_, "FIELD_TYPE", // position-0 references, so each caught type is still captured. local_var_type_resolves(prov, local, ref, t) :- java_type_reference(_, "LOCAL_VARIABLE", _, _, _, _, _, "0", _, _, _, _, _, _, _, local, "LOCAL_VARIABLE", ref), type_ref_resolves(prov, ref, t). -// A `var` local has no written type reference, so the rule above yields nothing. Infer +// A `var` local's only reference is written `var` and resolves to nothing, so the rule above +// yields nothing (and external-types.dl keeps it from naming an external type). Infer // the type from a direct object-creation initializer (var x = new Holder()): the // created type — carrying its written type arguments — becomes the local's resolved type, // which feeds callee resolution AND type-argument binding uniformly. Gated on the isVar diff --git a/graph/test/java/cases/61-var-local-type/src/probe/Handler.java b/graph/test/java/cases/61-var-local-type/src/probe/Handler.java new file mode 100644 index 000000000..bffaafc98 --- /dev/null +++ b/graph/test/java/cases/61-var-local-type/src/probe/Handler.java @@ -0,0 +1,5 @@ +package probe; + +public interface Handler { + void handle(); +} diff --git a/graph/test/java/cases/61-var-local-type/src/probe/Registry.java b/graph/test/java/cases/61-var-local-type/src/probe/Registry.java new file mode 100644 index 000000000..195d1eb6b --- /dev/null +++ b/graph/test/java/cases/61-var-local-type/src/probe/Registry.java @@ -0,0 +1,7 @@ +package probe; + +public class Registry { + public Handler lookup(String key) { + return null; + } +} diff --git a/graph/test/java/cases/61-var-local-type/src/probe/VarLocals.java b/graph/test/java/cases/61-var-local-type/src/probe/VarLocals.java new file mode 100644 index 000000000..bf5c79e01 --- /dev/null +++ b/graph/test/java/cases/61-var-local-type/src/probe/VarLocals.java @@ -0,0 +1,110 @@ +package probe; + +import java.text.Format; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; + +/** + * `var` is a reserved type name (JLS 14.4), so a local declared with it never has a type called + * `var`: its type is the initializer's. Each construct below has a control written with the + * declared type, which must resolve the same way. + */ +public class VarLocals { + + /** var from a client call's return: one known edge, no library label. */ + void fromCall(Registry r) { + var h = r.lookup("x"); + h.handle(); + } + + /** control: the same call with the type written down. */ + void fromCallDeclared(Registry r) { + Handler h = r.lookup("x"); + h.handle(); + } + + /** var from a static factory. */ + void fromFactory() { + var w = Widget.create(); + w.paint(); + } + + /** control: var from `new` was already pinned to the created type. */ + void fromNew() { + var w = new Widget(); + w.paint(); + } + + /** var from `new`, then reassigned: no longer pinned, still a Widget. */ + void fromNewReassigned() { + var w = new Widget(); + w = Widget.create(); + w.paint(); + } + + /** var from a literal: String's own method, and nothing named var. */ + int fromLiteral() { + var s = "abc"; + return s.length(); + } + + /** control: the literal itself, with no local; a var must answer the same way. */ + int literalInline() { + return "abc".length(); + } + + /** control: the declared String. */ + int fromLiteralDeclared() { + String s = "abc"; + return s.length(); + } + + /** var loop variable over a platform list (not staged): unresolved, not a library call. */ + void fromEnhancedFor(List ws) { + for (var x : ws) { + x.paint(); + } + } + + /** control: the declared loop variable. */ + void fromEnhancedForDeclared(List ws) { + for (Widget x : ws) { + x.paint(); + } + } + + /** var resource in try-with-resources. */ + void fromResource() { + try (var w = Widget.create()) { + w.paint(); + } + } + + /** var from an unstaged platform call: the type is unknown, so the call stays unresolved. */ + int fromUnknown() { + var xs = Collections.emptyList(); + return xs.size(); + } + + /** control: a declared external local still types its receiver as that external type. */ + String declaredExternal(Object o) { + Format f = source(); + return f.format(o); + } + + /** var from a client call declared to return an external type: that type, not `var`. */ + String varExternal(Object o) { + var f = source(); + return f.format(o); + } + + Format source() { + return null; + } + + /** var lambda parameters (Java 11). */ + BiConsumer lambdaParams() { + return (var a, var b) -> a.paint(); + } +} diff --git a/graph/test/java/cases/61-var-local-type/src/probe/Widget.java b/graph/test/java/cases/61-var-local-type/src/probe/Widget.java new file mode 100644 index 000000000..11ded4520 --- /dev/null +++ b/graph/test/java/cases/61-var-local-type/src/probe/Widget.java @@ -0,0 +1,14 @@ +package probe; + +public class Widget implements AutoCloseable { + public static Widget create() { + return new Widget(); + } + + public void paint() { + } + + @Override + public void close() { + } +} diff --git a/graph/test/java/expected/36-generic-two-hop.edges b/graph/test/java/expected/36-generic-two-hop.edges index 6322a7de8..dbfa17bee 100644 --- a/graph/test/java/expected/36-generic-two-hop.edges +++ b/graph/test/java/expected/36-generic-two-hop.edges @@ -5,16 +5,12 @@ boundary_lib method probe.TwoHop#nestedArgOnLibRef() -> dep.Lookup#entries() boundary_lib method probe.TwoHop#nestedArgOnLibRef() -> dep.Pair#value() boundary_lib method probe.TwoHop#oneHop() -> dep.Box#get(int) boundary_lib method probe.TwoHop#oneHopVar() -> dep.Box#get(int) -boundary_lib method probe.TwoHop#oneHopVar() -> external:var.get boundary_lib method probe.TwoHop#twoHopBothVars() -> dep.Box#iterator() boundary_lib method probe.TwoHop#twoHopBothVars() -> dep.Iter#next() -boundary_lib method probe.TwoHop#twoHopBothVars() -> external:var.iterator -boundary_lib method probe.TwoHop#twoHopBothVars() -> external:var.next boundary_lib method probe.TwoHop#twoHopChain() -> dep.Box#iterator() boundary_lib method probe.TwoHop#twoHopChain() -> dep.Iter#next() boundary_lib method probe.TwoHop#twoHopVar() -> dep.Box#iterator() boundary_lib method probe.TwoHop#twoHopVar() -> dep.Iter#next() -boundary_lib method probe.TwoHop#twoHopVar() -> external:var.next known_edge method probe.TwoHop#nestedArgOnClientRef() -> probe.TwoHop#pairs() known_edge method probe.TwoHop#nestedArgOnClientRef() -> probe.TwoHop.Widget#id() known_edge method probe.TwoHop#nestedArgOnLibRef() -> probe.TwoHop#lookup() diff --git a/graph/test/java/expected/61-var-local-type.edges b/graph/test/java/expected/61-var-local-type.edges new file mode 100644 index 000000000..dcbfc0084 --- /dev/null +++ b/graph/test/java/expected/61-var-local-type.edges @@ -0,0 +1,25 @@ +ambiguous_unknown method probe.VarLocals#fromEnhancedFor(List) -> - +ambiguous_unknown method probe.VarLocals#fromLiteral() -> - +ambiguous_unknown method probe.VarLocals#fromUnknown() -> - +ambiguous_unknown method probe.VarLocals#lambdaParams() -> - +ambiguous_unknown method probe.VarLocals#literalInline() -> - +boundary_lib method probe.VarLocals#declaredExternal(Object) -> external:java.text.Format.format +boundary_lib method probe.VarLocals#fromLiteralDeclared() -> external:String.length +boundary_lib method probe.VarLocals#varExternal(Object) -> external:java.text.Format.format +known_edge method probe.VarLocals#declaredExternal(Object) -> probe.VarLocals#source() +known_edge method probe.VarLocals#fromCall(Registry) -> probe.Handler#handle() +known_edge method probe.VarLocals#fromCall(Registry) -> probe.Registry#lookup(String) +known_edge method probe.VarLocals#fromCallDeclared(Registry) -> probe.Handler#handle() +known_edge method probe.VarLocals#fromCallDeclared(Registry) -> probe.Registry#lookup(String) +known_edge method probe.VarLocals#fromEnhancedForDeclared(List) -> probe.Widget#paint() +known_edge method probe.VarLocals#fromFactory() -> probe.Widget#create() +known_edge method probe.VarLocals#fromFactory() -> probe.Widget#paint() +known_edge method probe.VarLocals#fromNew() -> probe.Widget#paint() +known_edge method probe.VarLocals#fromNewReassigned() -> probe.Widget#create() +known_edge method probe.VarLocals#fromNewReassigned() -> probe.Widget#paint() +known_edge method probe.VarLocals#fromResource() -> probe.Widget#create() +known_edge method probe.VarLocals#fromResource() -> probe.Widget#paint() +known_edge method probe.VarLocals#varExternal(Object) -> probe.VarLocals#source() +known_edge new probe.VarLocals#fromNew() -> probe.Widget#() +known_edge new probe.VarLocals#fromNewReassigned() -> probe.Widget#() +known_edge new probe.Widget#create() -> probe.Widget#() diff --git a/graph/test/java/expected/61-var-local-type.type-use b/graph/test/java/expected/61-var-local-type.type-use new file mode 100644 index 000000000..e1b611ace --- /dev/null +++ b/graph/test/java/expected/61-var-local-type.type-use @@ -0,0 +1,25 @@ +ambiguous_unknown ANNOTATION_TYPE 0 probe.Widget [ANNOTATION] -> - +ambiguous_unknown IMPLEMENTS_INTERFACE 0 probe.Widget [TYPE] -> - +ambiguous_unknown LOCAL_VARIABLE 0 probe.VarLocals#declaredExternal(Object) [LOCAL_VARIABLE] -> - +ambiguous_unknown LOCAL_VARIABLE 0 probe.VarLocals#fromLiteralDeclared() [LOCAL_VARIABLE] -> - +ambiguous_unknown METHOD_PARAM 0 probe.Registry#lookup(String) [METHOD_PARAM] -> - +ambiguous_unknown METHOD_PARAM 0 probe.VarLocals#declaredExternal(Object) [METHOD_PARAM] -> - +ambiguous_unknown METHOD_PARAM 0 probe.VarLocals#fromEnhancedFor(List) [METHOD_PARAM] -> - +ambiguous_unknown METHOD_PARAM 0 probe.VarLocals#fromEnhancedForDeclared(List) [METHOD_PARAM] -> - +ambiguous_unknown METHOD_PARAM 0 probe.VarLocals#varExternal(Object) [METHOD_PARAM] -> - +ambiguous_unknown METHOD_RETURN 0 probe.VarLocals#declaredExternal(Object) [METHOD] -> - +ambiguous_unknown METHOD_RETURN 0 probe.VarLocals#lambdaParams() [METHOD] -> - +ambiguous_unknown METHOD_RETURN 0 probe.VarLocals#source() [METHOD] -> - +ambiguous_unknown METHOD_RETURN 0 probe.VarLocals#varExternal(Object) [METHOD] -> - +known_edge LOCAL_VARIABLE 0 probe.VarLocals#fromCallDeclared(Registry) [LOCAL_VARIABLE] -> probe.Handler +known_edge LOCAL_VARIABLE 0 probe.VarLocals#fromEnhancedForDeclared(List) [LOCAL_VARIABLE] -> probe.Widget +known_edge METHOD_PARAM 0 probe.VarLocals#fromCall(Registry) [METHOD_PARAM] -> probe.Registry +known_edge METHOD_PARAM 0 probe.VarLocals#fromCallDeclared(Registry) [METHOD_PARAM] -> probe.Registry +known_edge METHOD_PARAM 1 probe.VarLocals#fromEnhancedFor(List) [METHOD_PARAM] -> probe.Widget +known_edge METHOD_PARAM 1 probe.VarLocals#fromEnhancedForDeclared(List) [METHOD_PARAM] -> probe.Widget +known_edge METHOD_RETURN 0 probe.Registry#lookup(String) [METHOD] -> probe.Handler +known_edge METHOD_RETURN 0 probe.Widget#create() [METHOD] -> probe.Widget +known_edge METHOD_RETURN 1 probe.VarLocals#lambdaParams() [METHOD] -> probe.Widget +known_edge OBJECT_CREATION_TYPE 0 probe.VarLocals#fromNew() [EXPRESSION] -> probe.Widget +known_edge OBJECT_CREATION_TYPE 0 probe.VarLocals#fromNewReassigned() [EXPRESSION] -> probe.Widget +known_edge OBJECT_CREATION_TYPE 0 probe.Widget#create() [EXPRESSION] -> probe.Widget