Skip to content

Ruby: RBI library changes to support models-as-data model generation #9932

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 90 additions & 44 deletions ruby/ql/lib/codeql/ruby/experimental/Rbi.qll
Original file line number Diff line number Diff line change
Expand Up @@ -150,22 +150,14 @@ module Rbi {
class ProcCall extends RbiType, SignatureCall, MethodCallAgainstT {
ProcCall() { this.getMethodName() = "proc" }

private ProcReturnsTypeCall getReturnsTypeCall() { result.getProcCall() = this }

private ProcParamsCall getParamsCall() { result.getProcCall() = this }
override ReturnsTypeCall getReturnsTypeCall() {
result.(ProcReturnsTypeCall).getProcCall() = this
}

/**
* Gets the return type of this type signature.
*/
override ReturnType getReturnType() { result = this.getReturnsTypeCall().getReturnType() }
override ProcParamsCall getParamsCall() { result.getProcCall() = this }

/**
* Gets the type of a parameter of this type signature.
*/
override ParameterType getAParameterType() {
result = this.getParamsCall().getAParameterType()
}
// TODO: get associated method to which this can be passed
// TODO: widen type for procs/blocks
override MethodBase getAssociatedMethod() { none() }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this return Callable for procs/blocks/lambdas?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd probably want this to return a BlockParameter, e.g. in:

  sig do
    params(
      a: String,
      block: T.nilable(T.proc.params(b: Integer).void)
    ).void
  end
  def blk_nilable_method(a, &block); end

the T.proc call corresponds to the &block parameter of blk_nilable_method, whereas the sig call corresponds to the blk_nilable_method definition itself. So we maybe need something like newtype TTypedCallable = TypedMethod(MethodBase m) or TypedBlock(BlockParameter b) or maybe to add another predicate BlockParameter getAssociatedBlockParameter() to the SignatureCall abstract class and in practice only one of getAssociatedMethod() and getAssociatedBlockParameter() will have a value.

}
}

Expand Down Expand Up @@ -207,15 +199,11 @@ module Rbi {
* A call that defines a type signature for a method or proc.
*/
abstract class SignatureCall extends MethodCall {
/**
* Gets the return type of this type signature.
*/
abstract ReturnType getReturnType();
abstract ParamsCall getParamsCall();

/**
* Gets the type of a parameter of this type signature.
*/
abstract ParameterType getAParameterType();
abstract ReturnsTypeCall getReturnsTypeCall();

abstract MethodBase getAssociatedMethod();
}

private predicate isMethodSignatureCallNode(CfgNode n) {
Expand All @@ -240,20 +228,35 @@ module Rbi {
)
}

/**
* A call to a method named `attr_reader` or `attr_accessor`, used to define
* attribute reader methods for a named attribute.
*/
class AttrReaderMethodCall extends MethodCall {
AttrReaderMethodCall() { this.getMethodName() = ["attr_reader", "attr_accessor"] }

/** Gets a name of an attribute defined by this call. */
string getAnAttributeName() {
result = this.getAnArgument().getConstantValue().getStringlikeValue()
}
}

/** A call to `sig` to define the type signature of a method. */
class MethodSignatureCall extends SignatureCall {
MethodSignatureCall() { this.getMethodName() = "sig" }

private MethodReturnsTypeCall getReturnsTypeCall() { result.getMethodSignatureCall() = this }
override ReturnsTypeCall getReturnsTypeCall() {
result.(MethodReturnsTypeCall).getMethodSignatureCall() = this
}

private MethodParamsCall getParamsCall() { result.getMethodSignatureCall() = this }
override MethodParamsCall getParamsCall() { result.getMethodSignatureCall() = this }

private ExprCfgNode getCfgNode() { result.getExpr() = this }

/**
* Gets the method whose type signature is defined by this call.
*/
MethodBase getAssociatedMethod() {
override MethodBase getAssociatedMethod() {
result =
min(ExprCfgNode methodCfgNode, int i |
methodSignatureSuccessorNodeRanked(this.getCfgNode(), methodCfgNode, i) and
Expand All @@ -267,10 +270,10 @@ module Rbi {
* Gets a call to `attr_reader` or `attr_accessor` where the return type of
* the generated method is described by this call.
*/
MethodCall getAssociatedAttrReaderCall() {
AttrReaderMethodCall getAssociatedAttrReaderCall() {
result =
min(ExprNodes::MethodCallCfgNode c, int i |
c.getExpr().getMethodName() = ["attr_reader", "attr_accessor"] and
c.getExpr() instanceof AttrReaderMethodCall and
methodSignatureSuccessorNodeRanked(this.getCfgNode(), c, i)
|
c order by i
Expand All @@ -280,12 +283,7 @@ module Rbi {
/**
* Gets the return type of this type signature.
*/
override ReturnType getReturnType() { result = this.getReturnsTypeCall().getReturnType() }

/**
* Gets the type of a parameter of this type signature.
*/
override ParameterType getAParameterType() { result = this.getParamsCall().getAParameterType() }
ReturnType getReturnType() { result = this.getReturnsTypeCall().getReturnType() }
}

/**
Expand Down Expand Up @@ -320,12 +318,54 @@ module Rbi {
ParamsCall() { this.getMethodName() = "params" }

/**
* Gets the type of a parameter defined by this call.
* Gets the type of a positional parameter defined by this call.
*/
ParameterType getAParameterType() { result = this.getArgument(_) }
ParameterType getPositionalParameterType(int i) {
result = this.getArgument(i) and
// explicitly exclude keyword parameters
not this.getAssociatedParameter(result.getName()) instanceof KeywordParameter and
// and exclude block arguments
not this.getAssociatedParameter(result.getName()) instanceof BlockParameter
}

/** Gets the type of the keyword parameter named `keyword`. */
ParameterType getKeywordParameterType(string keyword) {
exists(KeywordParameter kp |
kp = this.getAssociatedParameter(keyword) and
kp.getName() = keyword and
result.getType() = this.getKeywordArgument(keyword)
)
}

/** Gets the type of the block parameter to the associated method. */
ParameterType getBlockParameterType() {
this.getAssociatedParameter(result.getName()) instanceof BlockParameter and
result = this.getArgument(_)
}

/** Gets the parameter with the given name. */
NamedParameter getAssociatedParameter(string name) {
result = this.getSignatureCall().getAssociatedMethod().getAParameter() and
result.getName() = name
}

/** Gets the signature call which this params call belongs to. */
SignatureCall getSignatureCall() { this = result.getParamsCall() }

/** Gets a parameter type associated with this call */
ParameterType getAParameterType() {
result = this.getPositionalParameterType(_) or
result = this.getKeywordParameterType(_) or
result = this.getBlockParameterType()
}
}

/**
* A call that defines a return type for an associated method.
* The return type is either a specific type, or the void type (i.e. "don't care").
*/
abstract class ReturnsTypeCall extends MethodCall {
/** Get the `ReturnType` corresponding to this call. */
abstract ReturnType getReturnType();
}

Expand Down Expand Up @@ -391,6 +431,7 @@ module Rbi {
abstract class ProcReturnsTypeCall extends ReturnsTypeCall, ProcSignatureDefiningCall { }

/** A call that defines the parameter types of a proc or block. */
// TODO: there is currently no way to map from this to parameter types with actual associated parameters
class ProcParamsCall extends ParamsCall, ProcSignatureDefiningCall { }

/** A call that defines the return type of a non-void proc or block. */
Expand All @@ -408,25 +449,30 @@ module Rbi {

/**
* A pair defining the type of a parameter to a method.
*
* This is an argument to some call to `params`.
*/
class ParameterType extends Pair {
private RbiType t;
private ParamsCall paramsCall;

ParameterType() { t = this.getValue() }
ParameterType() { paramsCall.getAnArgument() = this }

/** Gets the `RbiType` of this parameter. */
RbiType getType() { result = t }

private SignatureCall getOuterMethodSignatureCall() { this = result.getAParameterType() }
private SignatureCall getMethodSignatureCall() { paramsCall = result.getParamsCall() }

private MethodBase getAssociatedMethod() {
result = this.getOuterMethodSignatureCall().(MethodSignatureCall).getAssociatedMethod()
result = this.getMethodSignatureCall().(MethodSignatureCall).getAssociatedMethod()
}

/** Gets the parameter to which this type applies. */
/** Gets the `RbiType` of this parameter. */
RbiType getType() { result = this.getValue() }

/** Gets the name of this parameter. */
string getName() { result = this.getKey().getConstantValue().getStringlikeValue() }

/** Gets the `NamedParameter` to which this type applies. */
NamedParameter getParameter() {
result = this.getAssociatedMethod().getAParameter() and
result.getName() = this.getKey().getConstantValue().getStringlikeValue()
result.getName() = this.getName()
}
}
}
4 changes: 3 additions & 1 deletion ruby/ql/test/library-tests/experimental/Rbi.ql
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ query predicate hashTypes(RbiHashType ht, RbiType kt, RbiType vt) {
kt = ht.getKeyType() and vt = ht.getValueType()
}

query predicate signatureCalls(SignatureCall c, ReturnType r) { r = c.getReturnType() }
query predicate signatureCalls(SignatureCall c, ReturnType r) {
r = c.getReturnsTypeCall().getReturnType()
}

query predicate paramsCalls(ParamsCall c) { any() }

Expand Down