diff --git a/graph/java/engine/expression-resolution/overload.dl b/graph/java/engine/expression-resolution/overload.dl index 0e918839..e312a933 100644 --- a/graph/java/engine/expression-resolution/overload.dl +++ b/graph/java/engine/expression-resolution/overload.dl @@ -299,6 +299,41 @@ arg_value_type(call, pos, vt) :- java_expression(_, "ARGUMENT", _, _, _, _, call arg_value_type(call, pos, vt) :- java_expression(_, "ARGUMENT", _, _, _, _, call, pos, _, _, name, _, _, _, "FIELD", _, _, _, _, _, _, _, _, _, arg), expr_ultimate_type("client", arg, encl), java_field(name, vt, _, _, _, _, _, _, encl, _, _, _, _, _), value_type_name(vt). +// An ARITHMETIC or COMPARISON expression argument: `new Elements(elements.size() - 1)`, `remove(--i)`, `put(k, a == b)`. +// Its operands may be untyped (a call into an unstaged library), but the operator fixes what kind of value it is: +// `-` `*` `/` `%` and the shifts make a number, a comparison or `&&` / `||` / `!` a boolean. `+` is left out (a String +// concatenation is a `+` too), and so are `&` `|` `^` (numeric or boolean by operand). "numeric" is a value type of its +// own because the width is not known: it is compatible with every numeric primitive and wrapper, never with String, +// boolean or a reference type. Without it the call kept `Elements(Element...)` beside `Elements(int)`, and every +// caller of the one was a caller of the other. +// java_expression (25): 1 kind 2 edgeRole 7 parentExpressionHash 8 position 14 operatorString +.decl numeric_op(o:symbol) +.decl numeric_unary_op(o:symbol) +.decl boolean_op(o:symbol) +.decl numeric_prim(p:symbol) +.decl prim_value(v:symbol) +numeric_op("-"). numeric_op("*"). numeric_op("/"). numeric_op("%"). numeric_op("<<"). numeric_op(">>"). numeric_op(">>>"). +numeric_unary_op("-"). numeric_unary_op("+"). numeric_unary_op("++"). numeric_unary_op("--"). numeric_unary_op("~"). +boolean_op("=="). boolean_op("!="). boolean_op("<"). boolean_op(">"). boolean_op("<="). boolean_op(">="). boolean_op("&&"). boolean_op("||"). +arg_value_type(call, pos, "numeric") :- java_expression("BINARY_EXPRESSION", "ARGUMENT", _, _, _, _, call, pos, _, _, _, _, _, op, _, _, _, _, _, _, _, _, _, _, _), + numeric_op(op). +arg_value_type(call, pos, "numeric") :- java_expression("UNARY_EXPRESSION", "ARGUMENT", _, _, _, _, call, pos, _, _, _, _, _, op, _, _, _, _, _, _, _, _, _, _, _), + numeric_unary_op(op). +arg_value_type(call, pos, "boolean") :- java_expression("BINARY_EXPRESSION", "ARGUMENT", _, _, _, _, call, pos, _, _, _, _, _, op, _, _, _, _, _, _, _, _, _, _, _), + boolean_op(op). +arg_value_type(call, pos, "boolean") :- java_expression("UNARY_EXPRESSION", "ARGUMENT", _, _, _, _, call, pos, _, _, _, _, _, "!", _, _, _, _, _, _, _, _, _, _, _). +numeric_prim("int"). numeric_prim("long"). numeric_prim("float"). numeric_prim("double"). +numeric_prim("short"). numeric_prim("byte"). numeric_prim("char"). +value_compatible("numeric", b) :- value_prim(b, p), numeric_prim(p). +// A primitive value (a literal, a primitive variable, an arithmetic or comparison expression) never reaches a +// parameter typed as a CLIENT type: boxing makes a java.lang wrapper, which no project type is. +prim_value(v) :- value_prim(v, v), v != "String", v != "null". +prim_value("numeric"). +value_mismatch(call, callee) :- expr_call_candidate(call, callee), + arg_value_type(call, pos, at), prim_value(at), + param_type_hash(callee, pos, pt), type_prov(pt, "client"), + !arg_is_target_typed(call, pos). + // Parameter value-type (parameterTypeName col 4 is the simple name: int / String / ...). param_value_type(callee, pos, vt) :- java_method_parameter(_, pos, callee, _, vt, _, _, _, _, _, _, _, _), value_type_name(vt). param_value_type(callee, pos, vt) :- lib_method_parameter(_, pos, callee, _, vt, _, _, _, _, _, _, _, _), value_type_name(vt). diff --git a/graph/java/engine/resolution/external-types.dl b/graph/java/engine/resolution/external-types.dl index 4b83b6a1..46427be8 100644 --- a/graph/java/engine/resolution/external-types.dl +++ b/graph/java/engine/resolution/external-types.dl @@ -151,7 +151,24 @@ local_creation_init(local) :- java_expression("ANONYMOUS_CLASS_CREATION", "ROOT" local_reassigned(local) :- java_local_variable(name, _, _, _, _, _, _, _, _, _, _, _, _, m, _, _, _, _, local), java_expression(_, "ASSIGNMENT_TARGET", _, _, _, _, _, _, _, _, name, _, _, _, "LOCAL_VARIABLE", _, _, _, _, _, _, _, _, _, e), expr_ultimate_method("client", e, m). -local_creation_pinned(local) :- local_creation_init(local), !local_reassigned(local). +local_creation_pinned(local) :- local_creation_init(local), !local_reassigned(local), !local_creation_external(local). +// ...but a creation of a type no staged IR declares pins nothing: `final StringBuilder sb = new StringBuilder()` +// with no JDK staged has no flow type at all, so `sb.toString()` was an unresolved site -- and every unresolved +// `toString` / `append` was a by-name caller of each client method of that name. The declared type is then the +// answer, exactly as for an initializer that is not a creation: an external receiver, a library boundary. +.decl creation_ref_external(ref:symbol) +creation_ref_external(ref) :- java_type_reference(kind, "OBJECT_CREATION_TYPE", _, _, _, _, _, "0", tn, _, _, _, _, _, _, _, _, ref), + ext_ref_kind(kind), + tn != "", + ref_file(ref, tn, fp), + !name_resolves_in_file(tn, fp), + !simple_name_declared(tn), + !ref_names_tvar(ref). +.decl local_creation_external(local:symbol) +local_creation_external(local) :- + java_expression("OBJECT_CREATION", "ROOT", "LOCAL_VAR_INITIALIZER", "LOCAL_VARIABLE", _, local, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, obj), + java_type_reference(_, "OBJECT_CREATION_TYPE", _, _, _, _, _, "0", _, _, _, _, _, _, _, obj, "EXPRESSION", ref), + creation_ref_external(ref). expr_type("external", e, x) :- java_expression(_, _, _, _, _, _, _, _, _, _, name, _, _, _, "FIELD", _, _, _, _, _, _, _, _, _, e), expr_ultimate_type("client", e, encl), @@ -230,6 +247,20 @@ expr_call_candidate(call, m) :- call_site(call, name, recv), external_target(call, label) :- call_site(call, name, recv), expr_type("external", recv, x), label = cat(x, cat(".", name)). +// (a') the receiver is a TYPE NAME no staged IR declares: a static call on the platform or a dependency, +// `Character.charCount(cp)`, `Objects.equals(a, b)`, `Collections.unmodifiableMap(m)`. A static call never +// dispatches, so no client method runs behind it. The parser writes the receiver as an identifier it read as a +// type (not ambiguous with a variable) with the qualified name its imports give; left unresolved, each one was a +// by-name caller of every client method called `equals`, `valueOf`, `max`, `get`. +.decl external_static_receiver(recv:symbol, q:symbol) +external_static_receiver(recv, q) :- + java_expression("IDENTIFIER_REFERENCE", "RECEIVER", _, _, _, _, _, _, _, _, n, _, _, _, "TYPE", "", _, q, "false", _, _, _, _, _, recv), + q != "", n != "", + !qualified_name_declared(q), + !simple_name_declared(n). +external_target(call, label) :- call_site(call, name, recv), + external_static_receiver(recv, q), + label = cat("external:", cat(q, cat(".", name))). // The parser writes parameterCount as text, so the written arity is compared as text. call_arity(call, pc) :- call_argc(call, n), pc = to_string(n). call_arity(call, "0") :- call_site(call, _, _), !call_has_args(call). diff --git a/graph/test/java/expected/02-anonymous-sam.edges b/graph/test/java/expected/02-anonymous-sam.edges index 8eb9ab1d..e5812a01 100644 --- a/graph/test/java/expected/02-anonymous-sam.edges +++ b/graph/test/java/expected/02-anonymous-sam.edges @@ -1,10 +1,12 @@ ambiguous_anon anon_new AnonymousClasses#main(String[]) -> - ambiguous_unknown method AnonymousClasses#main(String[]) -> - -ambiguous_unknown method AnonymousClasses$anon:Comparator#compare(String,String) -> - ambiguous_unknown method AnonymousClasses$anon:Runnable#run() -> - ambiguous_unknown method AnonymousClasses$anon:Thread#run() -> - ambiguous_unknown new AnonymousClasses#main(String[]) -> - +boundary_lib method AnonymousClasses#main(String[]) -> external:java.util.List.of +boundary_lib method AnonymousClasses#main(String[]) -> external:java.util.List.sort boundary_lib method AnonymousClasses$anon:Comparator#compare(String,String) -> external:String.length +boundary_lib method AnonymousClasses$anon:Comparator#compare(String,String) -> external:java.lang.Integer.compare boundary_lib method AnonymousClasses$anon:Thread#run() -> external:Thread.getName known_edge method AnonymousClasses#main(String[]) -> AnonymousClasses$anon:Runnable#run() known_edge method AnonymousClasses#main(String[]) -> AnonymousClasses$anon:Thread#run() diff --git a/graph/test/java/expected/03-overloads-and-receivers.edges b/graph/test/java/expected/03-overloads-and-receivers.edges index 0c75dda2..d42d5882 100644 --- a/graph/test/java/expected/03-overloads-and-receivers.edges +++ b/graph/test/java/expected/03-overloads-and-receivers.edges @@ -1,8 +1,10 @@ ambiguous_anon anon_new testcases.engine.ComplexDispatch#() -> - -ambiguous_unknown method testcases.engine.ComplexDispatch#jdkStatics(String,int,int) -> - ambiguous_unknown new testcases.engine.ComplexDispatch#() -> - boundary_lib method testcases.engine.ComplexDispatch#boxingAndFields(String) -> external:java.util.Map.put boundary_lib method testcases.engine.ComplexDispatch#jdkStatics(String,int,int) -> external:String.length +boundary_lib method testcases.engine.ComplexDispatch#jdkStatics(String,int,int) -> external:java.lang.Integer.parseInt +boundary_lib method testcases.engine.ComplexDispatch#jdkStatics(String,int,int) -> external:java.lang.Math.max +boundary_lib method testcases.engine.ComplexDispatch#jdkStatics(String,int,int) -> external:java.lang.String.valueOf known_edge method testcases.engine.ComplexDispatch#internalOverloads(Throwable) -> testcases.engine.ComplexDispatch#emit(String) known_edge method testcases.engine.ComplexDispatch#internalOverloads(Throwable) -> testcases.engine.ComplexDispatch#log(String) known_edge method testcases.engine.ComplexDispatch#internalOverloads(Throwable) -> testcases.engine.ComplexDispatch#log(String,Throwable) diff --git a/graph/test/java/expected/05-lambda-and-method-refs.edges b/graph/test/java/expected/05-lambda-and-method-refs.edges index c0ac7cc9..65d3231f 100644 --- a/graph/test/java/expected/05-lambda-and-method-refs.edges +++ b/graph/test/java/expected/05-lambda-and-method-refs.edges @@ -3,6 +3,7 @@ ambiguous_unknown method Lam#viaForEach(String) -> - ambiguous_unknown method Lam#viaParamList(List) -> - ambiguous_unknown new Lam#() -> - ambiguous_unknown new Lam#main(String[]) -> - +boundary_lib method Lam#main(String[]) -> external:java.util.List.add boundary_lib method Lam#main(String[]) -> external:java.util.Map.put boundary_lib method Lam#viaForEach(String) -> external:java.util.Map.values boundary_lib method Lam#viaMethodRef(List) -> external:java.util.List.forEach diff --git a/graph/test/java/expected/12-library-interface-override.edges b/graph/test/java/expected/12-library-interface-override.edges index a75f030e..0dff072c 100644 --- a/graph/test/java/expected/12-library-interface-override.edges +++ b/graph/test/java/expected/12-library-interface-override.edges @@ -1,5 +1,6 @@ ambiguous_unknown method LibIfaceOverride#main(String[]) -> - ambiguous_unknown new LibIfaceOverride#main(String[]) -> - +boundary_lib method LibIfaceOverride#main(String[]) -> external:java.util.List.add boundary_lib method LibIfaceOverride#viaComparator(Comparator,String,String) -> external:java.util.Comparator.compare boundary_lib method LibIfaceOverride#viaFunction(Function,String) -> external:java.util.function.Function.apply boundary_lib method LibIfaceOverride#viaLibrarySort(List) -> external:java.util.List.sort diff --git a/graph/test/java/expected/13-type-qualified-statics.edges b/graph/test/java/expected/13-type-qualified-statics.edges index 525d2b29..c077724b 100644 --- a/graph/test/java/expected/13-type-qualified-statics.edges +++ b/graph/test/java/expected/13-type-qualified-statics.edges @@ -1,5 +1,6 @@ ambiguous_unknown method TypeQualifiedStatics#main(String[]) -> - -ambiguous_unknown method TypeQualifiedStatics#viaLibraryType(String) -> - +boundary_lib method TypeQualifiedStatics#viaLibraryType(String) -> external:java.lang.Integer.parseInt +boundary_lib method TypeQualifiedStatics#viaLibraryType(String) -> external:java.lang.String.valueOf known_edge method TypeQualifiedStatics#main(String[]) -> TypeQualifiedStatics#viaLibraryType(String) known_edge method TypeQualifiedStatics#main(String[]) -> TypeQualifiedStatics#viaNestedType(String) known_edge method TypeQualifiedStatics#main(String[]) -> TypeQualifiedStatics#viaOwnTypeName(String) diff --git a/graph/test/java/expected/14-lambda-param-callback.edges b/graph/test/java/expected/14-lambda-param-callback.edges index 5477eef8..e25dedab 100644 --- a/graph/test/java/expected/14-lambda-param-callback.edges +++ b/graph/test/java/expected/14-lambda-param-callback.edges @@ -1,9 +1,9 @@ -ambiguous_unknown method LambdaParamCallback#main(String[]) -> - ambiguous_unknown method LambdaParamCallback#viaMapValues(String) -> - ambiguous_unknown method LambdaParamCallback#viaStream(List,String) -> - ambiguous_unknown method LambdaParamCallback#viaTypedList(List,String) -> - ambiguous_unknown new LambdaParamCallback#() -> - ambiguous_unknown new LambdaParamCallback#main(String[]) -> - +boundary_lib method LambdaParamCallback#main(String[]) -> external:java.util.List.add boundary_lib method LambdaParamCallback#main(String[]) -> external:java.util.Map.put boundary_lib method LambdaParamCallback#viaMapValues(String) -> external:java.util.Map.values boundary_lib method LambdaParamCallback#viaStream(List,String) -> external:java.util.List.stream diff --git a/graph/test/java/expected/20-reflection-blind-spot.edges b/graph/test/java/expected/20-reflection-blind-spot.edges index fb79ed07..80c61dd8 100644 --- a/graph/test/java/expected/20-reflection-blind-spot.edges +++ b/graph/test/java/expected/20-reflection-blind-spot.edges @@ -6,7 +6,9 @@ boundary_lib method ReflectionSites#viaDynamicName(String) -> external:String.su boundary_lib method ReflectionSites#viaDynamicName(String) -> external:java.lang.reflect.Method.invoke boundary_lib method ReflectionSites#viaMethodInvoke(String) -> external:Class.getDeclaredConstructor boundary_lib method ReflectionSites#viaMethodInvoke(String) -> external:Class.getMethod +boundary_lib method ReflectionSites#viaMethodInvoke(String) -> external:java.lang.Class.forName boundary_lib method ReflectionSites#viaMethodInvoke(String) -> external:java.lang.reflect.Method.invoke +boundary_lib method ReflectionSites#viaNewInstance(String) -> external:java.lang.Class.forName boundary_lib method ReflectionSites#viaNewInstance(String) -> external:java.lang.reflect.Constructor.newInstance known_edge method ReflectionSites#main(String[]) -> ReflectionSites#viaDynamicName(String) known_edge method ReflectionSites#main(String[]) -> ReflectionSites#viaMethodInvoke(String) diff --git a/graph/test/java/expected/33-compiler-lowering.edges b/graph/test/java/expected/33-compiler-lowering.edges index f733fc16..2da65b27 100644 --- a/graph/test/java/expected/33-compiler-lowering.edges +++ b/graph/test/java/expected/33-compiler-lowering.edges @@ -1,12 +1,13 @@ -ambiguous_unknown method lowering.Lowering#explicitRequireNonNull(Sink) -> - -ambiguous_unknown method lowering.Lowering#tryWithResources(String) -> - ambiguous_unknown new lowering.Lowering#tryWithResources(String) -> - ambiguous_unknown new lowering.Lowering.Bag#() -> - boundary_lib method lowering.Lowering#boundRef(List,Sink) -> external:java.util.List.forEach boundary_lib method lowering.Lowering#explicitIterator(Bag) -> external:java.util.Iterator.hasNext boundary_lib method lowering.Lowering#explicitIterator(Bag) -> external:java.util.Iterator.next +boundary_lib method lowering.Lowering#explicitRequireNonNull(Sink) -> external:java.util.Objects.requireNonNull boundary_lib method lowering.Lowering#explicitUnbox(Integer) -> external:Integer.intValue boundary_lib method lowering.Lowering#plainLibraryCall(List) -> external:java.util.List.size +boundary_lib method lowering.Lowering#tryWithResources(String) -> external:java.io.StringReader.read +boundary_lib method lowering.Lowering#tryWithResources(String) -> external:java.lang.String.valueOf boundary_lib method lowering.Lowering#use(String) -> external:String.trim boundary_lib method lowering.Lowering.Bag#iterator() -> external:java.util.List.iterator boundary_lib method lowering.Lowering.Bag#size() -> external:java.util.List.size diff --git a/graph/test/java/expected/48-local-scope-shadowing.edges b/graph/test/java/expected/48-local-scope-shadowing.edges index 87e52222..506d49b9 100644 --- a/graph/test/java/expected/48-local-scope-shadowing.edges +++ b/graph/test/java/expected/48-local-scope-shadowing.edges @@ -1,6 +1,6 @@ -ambiguous_unknown method probe.Shadow#catchParameter() -> - ambiguous_unknown new probe.Shadow#catchParameter() -> - boundary_lib method probe.Shadow#catchParameter() -> external:RuntimeException.getMessage +boundary_lib method probe.Shadow#catchParameter() -> external:java.lang.String.valueOf boundary_lib method probe.Shadow#forHeader() -> external:String.isEmpty known_edge method probe.Shadow#after() -> probe.LocalSide#fromLocal() known_edge method probe.Shadow#catchParameter() -> probe.FieldSide#fromField() diff --git a/graph/test/java/expected/59-function-value-through-ternary.edges b/graph/test/java/expected/59-function-value-through-ternary.edges index 9003d6d0..d849e61c 100644 --- a/graph/test/java/expected/59-function-value-through-ternary.edges +++ b/graph/test/java/expected/59-function-value-through-ternary.edges @@ -1,6 +1,6 @@ -ambiguous_unknown method probe.App#() -> - -ambiguous_unknown method probe.App#callers() -> - +boundary_lib method probe.App#() -> external:java.lang.Boolean.getBoolean boundary_lib method probe.App#callOther(App,String) -> external:java.util.function.Function.apply +boundary_lib method probe.App#callers() -> external:java.lang.Boolean.getBoolean boundary_lib method probe.App#callers() -> external:java.util.function.Function.apply boundary_lib method probe.App#dispatch(String) -> external:java.util.function.Function.apply boundary_lib method probe.App#getPath(String) -> external:String.split diff --git a/graph/test/java/expected/61-var-local-type.edges b/graph/test/java/expected/61-var-local-type.edges index dd0eb4c0..0a171c39 100644 --- a/graph/test/java/expected/61-var-local-type.edges +++ b/graph/test/java/expected/61-var-local-type.edges @@ -6,6 +6,7 @@ ambiguous_unknown method probe.VarLocals#lambdaUnknownInterface() -> - 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#fromUnknown() -> external:java.util.Collections.emptyList boundary_lib method probe.VarLocals#lambdaLocal(Widget) -> external:java.util.function.Consumer.accept boundary_lib method probe.VarLocals#varExternal(Object) -> external:java.text.Format.format known_edge lambda_body probe.VarLocals#lambdaLocal(Widget) -> probe.Widget#paint() diff --git a/graph/test/java/expected/67-framework-registered-entry-points.edges b/graph/test/java/expected/67-framework-registered-entry-points.edges index 7b81a0c8..dc1b8be8 100644 --- a/graph/test/java/expected/67-framework-registered-entry-points.edges +++ b/graph/test/java/expected/67-framework-registered-entry-points.edges @@ -1,9 +1,9 @@ ambiguous_unknown method app.init.AppInit#onStartup(Set,ServletContext) -> - -ambiguous_unknown method app.rs.Size#valueOf(String) -> - ambiguous_unknown new app.init.BootConfig#bootServlet() -> - boundary_lib method app.init.AppInit#onStartup(Set,ServletContext) -> external:jakarta.servlet.ServletContext.addFilter boundary_lib method app.init.AppInit#onStartup(Set,ServletContext) -> external:jakarta.servlet.ServletContext.addListener boundary_lib method app.init.AppInit#onStartup(Set,ServletContext) -> external:jakarta.servlet.ServletContext.addServlet +boundary_lib method app.rs.Size#valueOf(String) -> external:java.lang.Integer.parseInt callback_registered callback app.init.AppInit#onStartup(Set,ServletContext) -> app.init.InitFilter#doFilter(ServletRequest,ServletResponse,FilterChain) known_edge method app.init.AppInit#wire(EventBus) -> app.init.EventBus#addListener(Class) known_edge new app.beans.AppConfig#plain() -> app.beans.Plain#() diff --git a/graph/test/java/expected/68-http-routes-and-sends.edges b/graph/test/java/expected/68-http-routes-and-sends.edges index 1e17c53b..6460aa77 100644 --- a/graph/test/java/expected/68-http-routes-and-sends.edges +++ b/graph/test/java/expected/68-http-routes-and-sends.edges @@ -1,9 +1,9 @@ -ambiguous_unknown method app.rs.RsClients#() -> - ambiguous_unknown method app.rs.RsClients#order(String) -> - ambiguous_unknown method app.rs.RsClients#orders() -> - ambiguous_unknown new app.rs.RsClients#() -> - ambiguous_unknown new app.spring.OrderClient#() -> - ambiguous_unknown new app.web.WebCaller#() -> - +boundary_lib method app.rs.RsClients#() -> external:jakarta.ws.rs.client.ClientBuilder.newClient boundary_lib method app.rs.RsClients#gadget(String) -> external:org.springframework.web.client.RestTemplate.getForObject boundary_lib method app.rs.RsClients#item(String) -> external:org.springframework.web.client.RestTemplate.getForObject boundary_lib method app.rs.RsClients#widget(String) -> external:org.springframework.web.client.RestTemplate.getForObject diff --git a/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-impact b/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-impact index 58ba1cbb..121c6716 100755 --- a/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-impact +++ b/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-impact @@ -808,6 +808,42 @@ class Impact: IMPORT_RE['.tsx'] = IMPORT_RE['.js'] = IMPORT_RE['.jsx'] = IMPORT_RE['.mjs'] = IMPORT_RE['.cjs'] = IMPORT_RE['.ts'] IMPORT_RE['.mts'] = IMPORT_RE['.cts'] = IMPORT_RE['.vue'] = IMPORT_RE['.svelte'] = IMPORT_RE['.ts'] # a component's