diff --git a/binary/extractor/cil/Semmle.Extraction.CSharp.IL/ILExtractor.cs b/binary/extractor/cil/Semmle.Extraction.CSharp.IL/ILExtractor.cs
index 352fe644f9ac..a3ce67d4d0f3 100644
--- a/binary/extractor/cil/Semmle.Extraction.CSharp.IL/ILExtractor.cs
+++ b/binary/extractor/cil/Semmle.Extraction.CSharp.IL/ILExtractor.cs
@@ -111,6 +111,11 @@ private void ExtractMethod(MethodDefinition method, int typeId) {
// Write access flags
trap.WriteTuple("cil_method_access_flags", methodId, (int)method.Attributes);
+ // Write parameter type signature for overload-precise identification
+ var methodParamTypes = string.Join(",",
+ method.Parameters.Select(p => p.ParameterType.FullName.Replace('/', '.')));
+ trap.WriteTuple("il_method_param_signature", methodId, $"({methodParamTypes})");
+
if (method.HasBody) {
ExtractMethodBody(method, methodId);
}
@@ -182,6 +187,10 @@ private void ExtractMethodBody(MethodDefinition method, int methodId) {
var targetMethodName = $"{declaringTypeName}.{methodRef.Name}";
trap.WriteTuple("il_call_target_unresolved", instrId, targetMethodName);
trap.WriteTuple("il_number_of_arguments", instrId, methodRef.Parameters.Count);
+ // Emit parameter type signature for overload-precise matching
+ var paramTypes = string.Join(",",
+ methodRef.Parameters.Select(p => p.ParameterType.FullName.Replace('/', '.')));
+ trap.WriteTuple("il_call_target_param_signature", instrId, $"({paramTypes})");
if(methodRef.MethodReturnType.ReturnType.MetadataType is not Mono.Cecil.MetadataType.Void) {
trap.WriteTuple("il_call_has_return_value", instrId);
}
diff --git a/binary/extractor/cil/semmlecode.binary.dbscheme b/binary/extractor/cil/semmlecode.binary.dbscheme
index 1332f79f579b..032083f1cd27 100644
--- a/binary/extractor/cil/semmlecode.binary.dbscheme
+++ b/binary/extractor/cil/semmlecode.binary.dbscheme
@@ -2467,6 +2467,28 @@ il_call_target_unresolved(
string target_method_name: string ref
);
+/**
+ * Parameter type signature for method definitions.
+ * The param_signature is a parenthesized, comma-separated list of fully-qualified
+ * parameter type names, e.g. "(System.String,System.Int32)" or "()" for no parameters.
+ * This enables overload-precise identification of methods during export.
+ */
+il_method_param_signature(
+ int method: @method ref,
+ string param_signature: string ref
+);
+
+/**
+ * Parameter type signature for unresolved method call targets.
+ * The param_signature is a parenthesized, comma-separated list of fully-qualified
+ * parameter type names, e.g. "(System.String,System.Int32)" or "()" for no parameters.
+ * This enables overload-precise matching of call targets.
+ */
+il_call_target_param_signature(
+ int instruction: @il_instruction ref,
+ string param_signature: string ref
+);
+
il_field_operand(
int instruction: @il_instruction ref,
string declaring_type_name: string ref,
@@ -2990,3 +3012,13 @@ jvm_stack_slot(
int slot: int ref,
int producer_id: @jvm_instruction ref
);
+
+/**
+ * Parameter type signature for JVM method call targets.
+ * The param_signature is a parenthesized, comma-separated list of human-readable
+ * parameter type names, e.g. "(Object,long,long)" or "()" for no parameters.
+ */
+jvm_call_target_param_signature(
+ int instruction: @jvm_instruction ref,
+ string param_signature: string ref
+);
diff --git a/binary/extractor/jvm/Semmle.Extraction.Java.ByteCode/JvmExtractor.cs b/binary/extractor/jvm/Semmle.Extraction.Java.ByteCode/JvmExtractor.cs
index 645a9a174146..ce851609515a 100644
--- a/binary/extractor/jvm/Semmle.Extraction.Java.ByteCode/JvmExtractor.cs
+++ b/binary/extractor/jvm/Semmle.Extraction.Java.ByteCode/JvmExtractor.cs
@@ -142,6 +142,10 @@ private void ExtractMethod(Method method, int typeId, ClassFile classFile, strin
// Extract access flags as raw bitmask
trap.WriteTuple("jvm_method_access_flags", methodId, (int)method.AccessFlags);
+ // Write parameter type signature for overload-precise identification
+ var descriptorUtf8ForSig = classFile.Constants.Get(method.Descriptor);
+ trap.WriteTuple("il_method_param_signature", methodId, ParseParamSignature(descriptorUtf8ForSig.Value));
+
// Check if this is a static method (for parameter indexing)
bool isStatic = (method.AccessFlags & AccessFlag.Static) != 0;
@@ -647,6 +651,12 @@ private void ExtractMethodRef(Instruction instr, int instrId, ClassFile classFil
int paramCount = CountParameters(descriptor);
trap.WriteTuple("jvm_number_of_arguments", instrId, paramCount);
+ // Write parameter type signature for overload-precise matching
+ if (!string.IsNullOrEmpty(descriptor))
+ {
+ trap.WriteTuple("jvm_call_target_param_signature", instrId, ParseParamSignature(descriptor));
+ }
+
if (!IsVoidReturn(descriptor))
{
trap.WriteTuple("jvm_call_has_return_value", instrId);
@@ -782,6 +792,66 @@ private static int CountParameters(string descriptor)
return count;
}
+ ///
+ /// Converts a JVM method descriptor to a parenthesized, comma-separated
+ /// parameter type signature, e.g. "(Ljava/lang/Object;JJ)V" becomes
+ /// "(Object,long,long)".
+ ///
+ private static string ParseParamSignature(string descriptor)
+ {
+ if (!descriptor.StartsWith("("))
+ return "(*)";
+
+ int closeParenIdx = descriptor.IndexOf(')');
+ if (closeParenIdx < 0)
+ return "(*)";
+
+ var paramPart = descriptor.Substring(1, closeParenIdx - 1);
+ var types = new System.Collections.Generic.List();
+ int i = 0;
+ while (i < paramPart.Length)
+ {
+ int arrayDims = 0;
+ while (i < paramPart.Length && paramPart[i] == '[')
+ {
+ arrayDims++;
+ i++;
+ }
+
+ if (i >= paramPart.Length)
+ break;
+
+ string baseType;
+ char c = paramPart[i];
+ switch (c)
+ {
+ case 'B': baseType = "byte"; i++; break;
+ case 'C': baseType = "char"; i++; break;
+ case 'D': baseType = "double"; i++; break;
+ case 'F': baseType = "float"; i++; break;
+ case 'I': baseType = "int"; i++; break;
+ case 'J': baseType = "long"; i++; break;
+ case 'S': baseType = "short"; i++; break;
+ case 'Z': baseType = "boolean"; i++; break;
+ case 'L':
+ int semiIdx = paramPart.IndexOf(';', i);
+ if (semiIdx < 0) semiIdx = paramPart.Length;
+ // Extract class name, convert / to ., strip leading L
+ baseType = paramPart.Substring(i + 1, semiIdx - i - 1).Replace('/', '.');
+ i = semiIdx + 1;
+ break;
+ default:
+ baseType = "?";
+ i++;
+ break;
+ }
+
+ types.Add(baseType + new string('[', arrayDims) + new string(']', arrayDims));
+ }
+
+ return "(" + string.Join(",", types) + ")";
+ }
+
private static bool IsVoidReturn(string descriptor)
{
return descriptor.EndsWith(")V");
diff --git a/binary/extractor/jvm/semmlecode.binary.dbscheme b/binary/extractor/jvm/semmlecode.binary.dbscheme
index e6cff58b0101..032083f1cd27 100644
--- a/binary/extractor/jvm/semmlecode.binary.dbscheme
+++ b/binary/extractor/jvm/semmlecode.binary.dbscheme
@@ -2467,6 +2467,28 @@ il_call_target_unresolved(
string target_method_name: string ref
);
+/**
+ * Parameter type signature for method definitions.
+ * The param_signature is a parenthesized, comma-separated list of fully-qualified
+ * parameter type names, e.g. "(System.String,System.Int32)" or "()" for no parameters.
+ * This enables overload-precise identification of methods during export.
+ */
+il_method_param_signature(
+ int method: @method ref,
+ string param_signature: string ref
+);
+
+/**
+ * Parameter type signature for unresolved method call targets.
+ * The param_signature is a parenthesized, comma-separated list of fully-qualified
+ * parameter type names, e.g. "(System.String,System.Int32)" or "()" for no parameters.
+ * This enables overload-precise matching of call targets.
+ */
+il_call_target_param_signature(
+ int instruction: @il_instruction ref,
+ string param_signature: string ref
+);
+
il_field_operand(
int instruction: @il_instruction ref,
string declaring_type_name: string ref,
@@ -2966,3 +2988,37 @@ jvm_method_access_flags(
unique int method: @method ref,
int flags: int ref
);
+
+/**
+ * Stack height at entry to a JVM instruction.
+ * This is computed by abstract interpretation during extraction.
+ */
+jvm_stack_height(
+ unique int instr: @jvm_instruction ref,
+ int height: int ref
+);
+
+/**
+ * Maps a stack slot at a specific instruction to the instruction that produced the value.
+ * slot 0 is the top of the stack, slot 1 is below that, etc.
+ * producer_id is the instruction ID that pushed this value onto the stack.
+ *
+ * This allows QL to determine data flow through the operand stack without
+ * expensive recursive CFG traversal.
+ */
+#keyset[instr, slot]
+jvm_stack_slot(
+ int instr: @jvm_instruction ref,
+ int slot: int ref,
+ int producer_id: @jvm_instruction ref
+);
+
+/**
+ * Parameter type signature for JVM method call targets.
+ * The param_signature is a parenthesized, comma-separated list of human-readable
+ * parameter type names, e.g. "(Object,long,long)" or "()" for no parameters.
+ */
+jvm_call_target_param_signature(
+ int instruction: @jvm_instruction ref,
+ string param_signature: string ref
+);
diff --git a/binary/ql/lib/semmle/code/binary/ast/internal/CilInstructions.qll b/binary/ql/lib/semmle/code/binary/ast/internal/CilInstructions.qll
index 786077adabf2..50805dddeeb6 100644
--- a/binary/ql/lib/semmle/code/binary/ast/internal/CilInstructions.qll
+++ b/binary/ql/lib/semmle/code/binary/ast/internal/CilInstructions.qll
@@ -141,6 +141,9 @@ class CilMethod extends @method {
result.getIndex() = i
}
+ /** Gets the parenthesized parameter type signature, e.g. `(System.String,System.Int32)`. */
+ string getParamSignature() { il_method_param_signature(this, result) }
+
CilType getDeclaringType() { methods(this, _, _, result) }
Location getLocation() { none() } // TODO: Extract
@@ -430,6 +433,9 @@ abstract class CilCallOrNewObject extends CilInstruction {
final int getNumberOfArguments() { il_number_of_arguments(this, result) }
final string getExternalName() { il_call_target_unresolved(this, result) }
+
+ /** Gets the parenthesized parameter type signature, e.g. `(System.String,System.Int32)`. */
+ final string getParamSignature() { il_call_target_param_signature(this, result) }
}
abstract class CilCall extends CilCallOrNewObject {
diff --git a/binary/ql/lib/semmle/code/binary/ast/internal/JvmInstructions.qll b/binary/ql/lib/semmle/code/binary/ast/internal/JvmInstructions.qll
index 51ac1f659505..14eac8b98759 100644
--- a/binary/ql/lib/semmle/code/binary/ast/internal/JvmInstructions.qll
+++ b/binary/ql/lib/semmle/code/binary/ast/internal/JvmInstructions.qll
@@ -64,6 +64,9 @@ class JvmMethod extends @method {
private string getSignature() { methods(this, _, result, _) }
+ /** Gets the parenthesized parameter type signature, e.g. `(Object,long,long)`. */
+ string getParamSignature() { il_method_param_signature(this, result) }
+
predicate isVoid() { this.getSignature().matches("%)V") }
JvmInstruction getAnInstruction() { jvm_instruction_method(result, this) }
@@ -1209,6 +1212,8 @@ class JvmPutfield extends @jvm_putfield, JvmFieldStore { }
abstract class JvmInvoke extends JvmInstruction {
string getCallTarget() { jvm_call_target_unresolved(this, result) }
+ string getParamSignature() { jvm_call_target_param_signature(this, result) }
+
int getNumberOfArguments() { jvm_number_of_arguments(this, result) }
predicate hasReturnValue() { jvm_call_has_return_value(this) }
diff --git a/binary/ql/lib/semmle/code/binary/ast/ir/IR.qll b/binary/ql/lib/semmle/code/binary/ast/ir/IR.qll
index ed01cdd84cb7..53215729b883 100644
--- a/binary/ql/lib/semmle/code/binary/ast/ir/IR.qll
+++ b/binary/ql/lib/semmle/code/binary/ast/ir/IR.qll
@@ -25,6 +25,9 @@ private module FinalInstruction {
predicate isPublic() { super.isPublic() }
+ /** Gets the parenthesized parameter type signature, e.g. `(System.String,System.Int32)`. */
+ string getParamSignature() { result = super.getParamSignature() }
+
/**
* Gets the fully qualified name of this method in the format:
* "Namespace.ClassName.MethodName".
@@ -302,6 +305,9 @@ private module FinalInstruction {
class ExternalRefInstruction extends Instruction instanceof Instruction::ExternalRefInstruction {
string getExternalName() { result = super.getExternalName() }
+ /** Gets the parenthesized parameter type signature, e.g. `(System.String,System.Int32)`. */
+ string getExternalParamSignature() { result = super.getExternalParamSignature() }
+
cached
predicate hasFullyQualifiedName(string namespace, string className, string methodName) {
exists(string s, string r |
diff --git a/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/Function.qll b/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/Function.qll
index 89ec7491e2e2..cd63bc61176f 100644
--- a/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/Function.qll
+++ b/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/Function.qll
@@ -25,5 +25,8 @@ class Function extends TFunction {
predicate isPublic() { f.isPublic() }
+ /** Gets the parenthesized parameter type signature, e.g. `(System.String,System.Int32)`. */
+ string getParamSignature() { result = f.getParamSignature() }
+
Type getDeclaringType() { result.getAFunction() = this }
}
diff --git a/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/Instruction.qll b/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/Instruction.qll
index 5654a9127df6..0d7e365f0673 100644
--- a/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/Instruction.qll
+++ b/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/Instruction.qll
@@ -176,6 +176,9 @@ class ExternalRefInstruction extends Instruction {
string getExternalName() { result = te.getExternalName(tag) }
+ /** Gets the parenthesized parameter type signature, e.g. `(System.String,System.Int32)`. */
+ string getExternalParamSignature() { result = te.getExternalParamSignature(tag) }
+
final override string getImmediateValue() { result = this.getExternalName() }
}
diff --git a/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/TranslatedElement.qll b/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/TranslatedElement.qll
index 7aa1466c0423..fa62e00b2e57 100644
--- a/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/TranslatedElement.qll
+++ b/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/TranslatedElement.qll
@@ -263,6 +263,12 @@ abstract class TranslatedElement extends TTranslatedElement {
*/
string getExternalName(InstructionTag tag) { none() }
+ /**
+ * Gets the parameter type signature for an external call with the given tag, e.g.
+ * `(System.String,System.Int32)`. This `tag` must refer to an `ExternalRef` instruction.
+ */
+ string getExternalParamSignature(InstructionTag tag) { none() }
+
/**
* Gets the name of the field referenced by an instruction with the given tag. This `tag` must refer to
* a `FieldAddress` instruction (that is, an instruction for which
diff --git a/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/TranslatedFunction.qll b/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/TranslatedFunction.qll
index a7faa107ef31..6df91a4cccdd 100644
--- a/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/TranslatedFunction.qll
+++ b/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/TranslatedFunction.qll
@@ -47,6 +47,9 @@ abstract class TranslatedFunction extends TranslatedElement {
abstract string getName();
+ /** Gets the parenthesized parameter type signature, e.g. `(System.String,System.Int32)`. */
+ abstract string getParamSignature();
+
final override string toString() { result = "Translation of " + this.getName() }
abstract predicate isProgramEntryPoint();
@@ -116,6 +119,9 @@ class TranslatedX86Function extends TranslatedFunction, TTranslatedX86Function {
final override predicate isPublic() { entry instanceof Raw::X86ExportedEntryInstruction }
+ // x86 does not have parameter type signatures
+ final override string getParamSignature() { result = "*" }
+
final override predicate hasOrdering(LocalVariableTag tag, int ordering) {
exists(Raw::X86Register r | tag = X86RegisterTag(r) |
// TODO: This hardcodes X64 calling convention for Windows
@@ -217,6 +223,8 @@ class TranslatedCilMethod extends TranslatedFunction, TTranslatedCilMethod {
override string getName() { result = method.getName() }
+ override string getParamSignature() { result = method.getParamSignature() }
+
override predicate isProgramEntryPoint() { none() }
override predicate isPublic() { method.isPublic() }
@@ -321,6 +329,8 @@ class TranslatedJvmMethod extends TranslatedFunction, TTranslatedJvmMethod {
override string getName() { result = method.getName() }
+ override string getParamSignature() { result = method.getParamSignature() }
+
override predicate isProgramEntryPoint() { none() }
override predicate isPublic() { method.isPublic() }
diff --git a/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/TranslatedInstruction.qll b/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/TranslatedInstruction.qll
index 86f0715750cd..44250b356353 100644
--- a/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/TranslatedInstruction.qll
+++ b/binary/ql/lib/semmle/code/binary/ast/ir/internal/Instruction0/TranslatedInstruction.qll
@@ -2169,6 +2169,12 @@ class TranslatedCilCall extends TranslatedCilInstruction, TTranslatedCilCall {
result = instr.getExternalName()
}
+ override string getExternalParamSignature(InstructionTag tag) {
+ not exists(instr.getTarget()) and
+ tag = CilCallTargetTag() and
+ result = instr.getParamSignature()
+ }
+
override Instruction getChildSuccessor(TranslatedElement child, SuccessorType succType) { none() }
override Instruction getSuccessor(InstructionTag tag, SuccessorType succType) {
@@ -2432,6 +2438,12 @@ class TranslatedNewObject extends TranslatedCilInstruction, TTranslatedNewObject
result = instr.getExternalName()
}
+ override string getExternalParamSignature(InstructionTag tag) {
+ not exists(instr.getConstructor()) and
+ tag = CilCallTargetTag() and
+ result = instr.getParamSignature()
+ }
+
override predicate hasTempVariable(TempVariableTag tag) {
tag = CilNewObjInitVarTag()
or
@@ -2757,6 +2769,11 @@ class TranslatedJvmInvoke extends TranslatedJvmInstruction, TTranslatedJvmInvoke
result = instr.getCallTarget()
}
+ final override string getExternalParamSignature(InstructionTag tag) {
+ tag = JvmCallTargetTag() and
+ result = instr.getParamSignature()
+ }
+
override Instruction getChildSuccessor(TranslatedElement child, SuccessorType succType) { none() }
override Instruction getSuccessor(InstructionTag tag, SuccessorType succType) {
diff --git a/binary/ql/lib/semmle/code/binary/ast/ir/internal/InstructionSig.qll b/binary/ql/lib/semmle/code/binary/ast/ir/internal/InstructionSig.qll
index 7352b0923f91..813c0480e0e3 100644
--- a/binary/ql/lib/semmle/code/binary/ast/ir/internal/InstructionSig.qll
+++ b/binary/ql/lib/semmle/code/binary/ast/ir/internal/InstructionSig.qll
@@ -34,6 +34,9 @@ signature module InstructionSig {
Type getDeclaringType();
predicate isPublic();
+
+ /** Gets the parenthesized parameter type signature, e.g. `(System.String,System.Int32)`. */
+ string getParamSignature();
}
class Operand {
@@ -202,6 +205,9 @@ signature module InstructionSig {
class ExternalRefInstruction extends Instruction {
string getExternalName();
+
+ /** Gets the parenthesized parameter type signature, e.g. `(System.String,System.Int32)`. */
+ string getExternalParamSignature();
}
class SubInstruction extends BinaryInstruction;
diff --git a/binary/ql/lib/semmle/code/binary/ast/ir/internal/TransformInstruction/TransformInstruction.qll b/binary/ql/lib/semmle/code/binary/ast/ir/internal/TransformInstruction/TransformInstruction.qll
index 77dd554c5f88..62a5bab72d28 100644
--- a/binary/ql/lib/semmle/code/binary/ast/ir/internal/TransformInstruction/TransformInstruction.qll
+++ b/binary/ql/lib/semmle/code/binary/ast/ir/internal/TransformInstruction/TransformInstruction.qll
@@ -184,6 +184,9 @@ module Transform {
Type getDeclaringType() { result = super.getDeclaringType() }
predicate isPublic() { super.isPublic() }
+
+ /** Gets the parenthesized parameter type signature, e.g. `(System.String,System.Int32)`. */
+ string getParamSignature() { result = super.getParamSignature() }
}
class Type instanceof Input::Type {
@@ -730,6 +733,14 @@ module Transform {
result = extRef.getExternalName()
)
}
+
+ /** Gets the parenthesized parameter type signature, e.g. `(System.String,System.Int32)`. */
+ string getExternalParamSignature() {
+ exists(Input::ExternalRefInstruction extRef |
+ this = TOldInstruction(extRef) and
+ result = extRef.getExternalParamSignature()
+ )
+ }
}
class FieldAddressInstruction extends Instruction {
diff --git a/binary/ql/lib/semmlecode.binary.dbscheme b/binary/ql/lib/semmlecode.binary.dbscheme
index e6cff58b0101..032083f1cd27 100644
--- a/binary/ql/lib/semmlecode.binary.dbscheme
+++ b/binary/ql/lib/semmlecode.binary.dbscheme
@@ -2467,6 +2467,28 @@ il_call_target_unresolved(
string target_method_name: string ref
);
+/**
+ * Parameter type signature for method definitions.
+ * The param_signature is a parenthesized, comma-separated list of fully-qualified
+ * parameter type names, e.g. "(System.String,System.Int32)" or "()" for no parameters.
+ * This enables overload-precise identification of methods during export.
+ */
+il_method_param_signature(
+ int method: @method ref,
+ string param_signature: string ref
+);
+
+/**
+ * Parameter type signature for unresolved method call targets.
+ * The param_signature is a parenthesized, comma-separated list of fully-qualified
+ * parameter type names, e.g. "(System.String,System.Int32)" or "()" for no parameters.
+ * This enables overload-precise matching of call targets.
+ */
+il_call_target_param_signature(
+ int instruction: @il_instruction ref,
+ string param_signature: string ref
+);
+
il_field_operand(
int instruction: @il_instruction ref,
string declaring_type_name: string ref,
@@ -2966,3 +2988,37 @@ jvm_method_access_flags(
unique int method: @method ref,
int flags: int ref
);
+
+/**
+ * Stack height at entry to a JVM instruction.
+ * This is computed by abstract interpretation during extraction.
+ */
+jvm_stack_height(
+ unique int instr: @jvm_instruction ref,
+ int height: int ref
+);
+
+/**
+ * Maps a stack slot at a specific instruction to the instruction that produced the value.
+ * slot 0 is the top of the stack, slot 1 is below that, etc.
+ * producer_id is the instruction ID that pushed this value onto the stack.
+ *
+ * This allows QL to determine data flow through the operand stack without
+ * expensive recursive CFG traversal.
+ */
+#keyset[instr, slot]
+jvm_stack_slot(
+ int instr: @jvm_instruction ref,
+ int slot: int ref,
+ int producer_id: @jvm_instruction ref
+);
+
+/**
+ * Parameter type signature for JVM method call targets.
+ * The param_signature is a parenthesized, comma-separated list of human-readable
+ * parameter type names, e.g. "(Object,long,long)" or "()" for no parameters.
+ */
+jvm_call_target_param_signature(
+ int instruction: @jvm_instruction ref,
+ string param_signature: string ref
+);
diff --git a/binary/ql/src/VulnerableCalls/VulnerableCalls.qll b/binary/ql/src/VulnerableCalls/VulnerableCalls.qll
index 8a4041bcdc6d..e44e736fbfa5 100644
--- a/binary/ql/src/VulnerableCalls/VulnerableCalls.qll
+++ b/binary/ql/src/VulnerableCalls/VulnerableCalls.qll
@@ -7,13 +7,17 @@ private import binary
private import semmle.code.binary.ast.ir.IR
/**
- * Holds if any call identified by `(namespace, className, methodName)` should be flagged
- * as potentially vulnerable, for reasons explained by the advisory with the given `id`.
+ * Holds if any call identified by `(namespace, className, methodName, paramSignature)` should be
+ * flagged as potentially vulnerable, for reasons explained by the advisory with the given `id`.
+ *
+ * `paramSignature` is a comma-separated list of fully-qualified parameter types enclosed in
+ * parentheses, e.g. `(System.String,System.Int32)`. An empty signature `()` matches methods
+ * with no parameters. A wildcard `*` matches any overload.
*
* This is an extensible predicate - values are provided via YAML data extensions.
*/
extensible predicate vulnerableCallModel(
- string namespace, string className, string methodName, string id
+ string namespace, string className, string methodName, string paramSignature, string id
);
/**
@@ -23,12 +27,19 @@ class VulnerableMethodCall extends CallInstruction {
string vulnerabilityId;
VulnerableMethodCall() {
- exists(string namespace, string className, string methodName |
- vulnerableCallModel(namespace, className, methodName, vulnerabilityId) and
- this.getTargetOperand()
- .getAnyDef()
- .(ExternalRefInstruction)
- .hasFullyQualifiedName(namespace, className, methodName)
+ exists(string namespace, string className, string methodName, string paramSignature |
+ vulnerableCallModel(namespace, className, methodName, paramSignature, vulnerabilityId) and
+ exists(ExternalRefInstruction extRef |
+ extRef = this.getTargetOperand().getAnyDef() and
+ extRef.hasFullyQualifiedName(namespace, className, methodName) and
+ (
+ paramSignature = "*"
+ or
+ extRef.getExternalParamSignature() = paramSignature
+ or
+ not exists(extRef.getExternalParamSignature()) // JVM calls lack param signatures
+ )
+ )
)
}
@@ -86,9 +97,24 @@ Function getStateMachineImplementation(Function stub) { isStateMachineImplementa
* state machine implementations.
*/
Function getAVulnerableMethod(string id) {
- // Direct call to vulnerable method
+ // Direct call to vulnerable method (cross-assembly via ExternalRef)
result = getADirectlyVulnerableMethod(id)
or
+ // Method defined in this binary that matches the model.
+ // This handles root cause mode where the vulnerable method is in the same
+ // package being analyzed, not referenced cross-assembly via ExternalRef.
+ // The result set includes the root cause methods themselves plus all their
+ // transitive callers, filtered downstream to public methods for export.
+ exists(string namespace, string className, string methodName, string paramSignature |
+ vulnerableCallModel(namespace, className, methodName, paramSignature, id) and
+ result.hasFullyQualifiedName(namespace, className, methodName) and
+ (
+ paramSignature = "*" or
+ result.getParamSignature() = paramSignature or
+ result.getParamSignature() = "*" // JVM functions don't have param signatures yet
+ )
+ )
+ or
// Transitive: method calls another method that is vulnerable (via ExternalRef for external calls)
exists(CallInstruction call, Function callee |
call.getEnclosingFunction() = result and
@@ -125,26 +151,30 @@ Function getAPublicVulnerableMethod(string id) {
*/
module ExportedVulnerableCalls {
/**
- * Holds if `(namespace, className, methodName)` identifies a method that
+ * Holds if `(namespace, className, methodName, paramSignature)` identifies a method that
* leads to a vulnerable call identified by `id`.
*/
- predicate pathToVulnerableMethod(string namespace, string className, string methodName, string id) {
+ predicate pathToVulnerableMethod(
+ string namespace, string className, string methodName, string paramSignature, string id
+ ) {
exists(Function m |
m = getAVulnerableMethod(id) and
- m.hasFullyQualifiedName(namespace, className, methodName)
+ m.hasFullyQualifiedName(namespace, className, methodName) and
+ paramSignature = m.getParamSignature()
)
}
/**
- * Holds if `(namespace, className, methodName)` identifies a public method
+ * Holds if `(namespace, className, methodName, paramSignature)` identifies a public method
* that leads to a vulnerable call identified by `id`.
*/
predicate publicPathToVulnerableMethod(
- string namespace, string className, string methodName, string id
+ string namespace, string className, string methodName, string paramSignature, string id
) {
exists(Function m |
m = getAPublicVulnerableMethod(id) and
- m.hasFullyQualifiedName(namespace, className, methodName)
+ m.hasFullyQualifiedName(namespace, className, methodName) and
+ paramSignature = m.getParamSignature()
)
}
}
diff --git a/binary/ql/src/VulnerableCalls/VulnerableCallsSummarize.ql b/binary/ql/src/VulnerableCalls/VulnerableCallsSummarize.ql
index 5def9a503c9c..e689c9f61dbf 100644
--- a/binary/ql/src/VulnerableCalls/VulnerableCallsSummarize.ql
+++ b/binary/ql/src/VulnerableCalls/VulnerableCallsSummarize.ql
@@ -13,17 +13,21 @@ import semmle.code.binary.ast.ir.IR
* Exports all methods that can reach vulnerable calls.
* Output format matches the vulnerableCallModel extensible predicate for iterative analysis.
*/
-query predicate vulnerableCallModel(string namespace, string className, string methodName, string id) {
- ExportedVulnerableCalls::pathToVulnerableMethod(namespace, className, methodName, id)
+query predicate vulnerableCallModel(
+ string namespace, string className, string methodName, string paramSignature, string id
+) {
+ ExportedVulnerableCalls::pathToVulnerableMethod(namespace, className, methodName, paramSignature,
+ id)
}
/**
* Exports only public methods that reach vulnerable calls (for API surface analysis).
*/
query predicate publicVulnerableCallModel(
- string namespace, string className, string methodName, string id
+ string namespace, string className, string methodName, string paramSignature, string id
) {
- ExportedVulnerableCalls::publicPathToVulnerableMethod(namespace, className, methodName, id)
+ ExportedVulnerableCalls::publicPathToVulnerableMethod(namespace, className, methodName,
+ paramSignature, id)
}
/**
diff --git a/binary/ql/src/VulnerableCalls/codeql-pack.lock.yml b/binary/ql/src/VulnerableCalls/codeql-pack.lock.yml
new file mode 100644
index 000000000000..53004274575d
--- /dev/null
+++ b/binary/ql/src/VulnerableCalls/codeql-pack.lock.yml
@@ -0,0 +1,4 @@
+---
+lockVersion: 1.0.0
+dependencies: {}
+compiled: false
diff --git a/binary/ql/src/VulnerableCalls/models/java-test-model.yml b/binary/ql/src/VulnerableCalls/models/java-test-model.yml
index 0e57e8f8f9ea..a677ab018f49 100644
--- a/binary/ql/src/VulnerableCalls/models/java-test-model.yml
+++ b/binary/ql/src/VulnerableCalls/models/java-test-model.yml
@@ -3,4 +3,4 @@ extensions:
pack: binary/vulnerable-calls
extensible: vulnerableCallModel
data:
- - ["java.io", "PrintStream", "println", "TEST-JAVA-001"]
+ - ["java.io", "PrintStream", "println", "*", "TEST-JAVA-001"]
diff --git a/binary/ql/src/VulnerableCalls/models/test-model.yml b/binary/ql/src/VulnerableCalls/models/test-model.yml
index d43627c1ed3d..076b5e90e37d 100644
--- a/binary/ql/src/VulnerableCalls/models/test-model.yml
+++ b/binary/ql/src/VulnerableCalls/models/test-model.yml
@@ -3,4 +3,4 @@ extensions:
pack: binary/vulnerable-calls
extensible: vulnerableCallModel
data:
- - ["System", "Console", "WriteLine", "TEST-VULN-001"]
+ - ["System", "Console", "WriteLine", "*", "TEST-VULN-001"]