Skip to content
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

opaque pointer support (type inference) for c #1323

Merged
merged 77 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
f3761ea
find type assert
jumormt Jan 6, 2024
18970cb
add assertion checking num field
jumormt Jan 6, 2024
40b8def
fix indirect call does not have called func
jumormt Jan 6, 2024
19fc337
add type check option
jumormt Jan 8, 2024
f57fc37
add type check option && first version of collect types
jumormt Jan 8, 2024
f38e36a
add "store (value operand) <- gep" rule
jumormt Jan 8, 2024
9c8d789
add inter-procedural rule
jumormt Jan 8, 2024
8e13066
add some comments
jumormt Jan 8, 2024
5144f86
fix call base bug
jumormt Jan 8, 2024
fae7e57
add callsite to callee inference
jumormt Jan 8, 2024
8934f21
do not propagate to declare func
jumormt Jan 8, 2024
4fcfafe
fix vararg
jumormt Jan 8, 2024
14efeb5
refactor
jumormt Jan 9, 2024
27fb0ea
use hash map to cache results
jumormt Jan 9, 2024
2e6d12d
refine
jumormt Jan 9, 2024
6f62afb
iterative memorizing dfs
jumormt Jan 9, 2024
7279bee
iterative memorizing dfs
jumormt Jan 9, 2024
5edb5f2
fix bug
jumormt Jan 9, 2024
3138be2
add comments and fix bug for c++20
jumormt Jan 9, 2024
7cad38a
fix deadloop
jumormt Jan 9, 2024
cc5f2aa
fix deadloop
jumormt Jan 9, 2024
28b941e
refine coding
jumormt Jan 9, 2024
f1cab1a
refactor inferTypeOfHeapObjOrStaticObj
jumormt Jan 9, 2024
8c838bf
refactor inferTypeOfHeapObjOrStaticObj
jumormt Jan 9, 2024
87b671c
add a new typeinference class
jumormt Jan 10, 2024
d38f373
refactor
jumormt Jan 10, 2024
076ca38
fix bug
jumormt Jan 10, 2024
8d15f3f
add debug support
jumormt Jan 10, 2024
cf89135
add default type
jumormt Jan 10, 2024
4712b6b
move getptrelementtype to test
jumormt Jan 10, 2024
550ebb1
move getClassNameOfThisPtr to chg builder and add diff test
jumormt Jan 10, 2024
3063af8
remove getelementptr in SVFExt and llvmmodule
jumormt Jan 10, 2024
376d729
return max field for ptr type
jumormt Jan 11, 2024
fd0e4d4
default for heap is i8
jumormt Jan 11, 2024
9a89761
for param of ext api, first find source and then derive type
jumormt Jan 11, 2024
868e023
refactor getOrInferLLVMObjType
jumormt Jan 11, 2024
9f45f32
skip global function value -> callsite when forward infer type
jumormt Jan 11, 2024
578a379
update comment
jumormt Jan 11, 2024
ef078d7
fix passing a function as a param
jumormt Jan 11, 2024
8889fd0
rename typesizedifftest, remove unnecessary getfirstcast
jumormt Jan 12, 2024
ef8ccd1
remove getptrelement and getpointerto in svf/*
jumormt Jan 12, 2024
058f626
ptr in svf main
jumormt Jan 12, 2024
b13b37f
thisptr class name prepare
jumormt Jan 12, 2024
8639716
infer type based on c++ constructor
jumormt Jan 13, 2024
ec19b79
add comments
jumormt Jan 13, 2024
1d33ec4
getOrInferThisPtrClassName update
jumormt Jan 13, 2024
d17948b
refine type diff test
jumormt Jan 14, 2024
5f35694
refine type diff test
jumormt Jan 14, 2024
1d52b55
fix indirect call passing
jumormt Jan 14, 2024
f854a53
separate cpp source and allocation
jumormt Jan 15, 2024
1857464
refactor iscpp constructor
jumormt Jan 15, 2024
a243d10
update
jumormt Jan 15, 2024
8efc471
for c++ fw type inference, consider constructor for now
jumormt Jan 15, 2024
04d89a8
update
jumormt Jan 15, 2024
f3a4f8e
delete c++
jumormt Jan 15, 2024
6cf0f73
delete c++
jumormt Jan 15, 2024
146fbd8
update based on comments
jumormt Jan 16, 2024
ab8963d
rename
jumormt Jan 16, 2024
ab04330
refactor
jumormt Jan 17, 2024
0aeeacb
reformat
jumormt Jan 17, 2024
76cdfc3
refactor
jumormt Jan 17, 2024
22dd059
move typeinference to LLVMModuleSet
jumormt Jan 21, 2024
12b3431
release llvm modouleset immediately after svfir is built
jumormt Jan 21, 2024
7352b98
remove static method
jumormt Jan 21, 2024
c363ad3
fix ander diff test
jumormt Jan 21, 2024
6ffb132
fix saber
jumormt Jan 21, 2024
3cdc19e
bytesize default 1
jumormt Jan 24, 2024
37f43f2
fix wpa release llvm module
jumormt Jan 24, 2024
8118f53
fix mac CI
jumormt Jan 24, 2024
eb34d0a
delete cpp
jumormt Jan 24, 2024
a176ca5
move release llvmmoduleset to the end of main
jumormt Jan 24, 2024
63aa46e
merge SVF master into opaque c
jumormt Jan 24, 2024
4d2f565
rename typeinference to objtypeinference
jumormt Jan 25, 2024
82f2e31
enable type check by default
jumormt Jan 25, 2024
9e61dd0
refactor
jumormt Jan 25, 2024
b0980a0
rename cpp and add ctest type infer CI
jumormt Jan 25, 2024
d620c42
add some assertions for npd
jumormt Jan 25, 2024
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
Prev Previous commit
Next Next commit
update based on comments
  • Loading branch information
jumormt committed Jan 24, 2024
commit 146fbd8cfa36ad0810b27dfb49a5a51229903459
10 changes: 1 addition & 9 deletions svf-llvm/include/SVF-LLVM/LLVMUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@
#include "SVFIR/SVFValue.h"
#include "Util/ThreadAPI.h"

#define VALUE_WITH_DBGINFO(value) \
LLVMUtil::dumpValue(value) + LLVMUtil::getSourceLoc(value)

namespace SVF
{
Expand Down Expand Up @@ -133,13 +131,6 @@ static inline Type* getPtrElementType(const PointerType* pty)
u32_t getNumOfElements(const Type* ety);


/// Select the largest (conservative) type from all types
const Type* selectLargestType(std::vector<const Type*>& objTys);

u32_t getArgNoInCallBase(const CallBase* callBase, const Value* arg);



/// Return true if this value refers to a object
bool isObject(const Value* ref);

Expand Down Expand Up @@ -473,6 +464,7 @@ std::string dumpValue(const Value* val);

std::string dumpType(const Type* type);

std::string dumpValueAndDbgInfo(const Value* val);

/**
* See more: https://github.com/SVF-tools/SVF/pull/1191
Expand Down
4 changes: 2 additions & 2 deletions svf-llvm/include/SVF-LLVM/SymbolTableBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ class SymbolTableBuilder
// @}


std::unique_ptr<TypeInference> & getTypeInference();
TypeInference* getTypeInference();

/// Forward collect all possible infer sites starting from a value
const Type* getOrInferLLVMObjType(const Value *startValue);
const Type* inferObjType(const Value *startValue);

/// Get the reference type of heap/static object from an allocation site.
//@{
Expand Down
23 changes: 17 additions & 6 deletions svf-llvm/include/SVF-LLVM/TypeInference.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class TypeInference {


private:
static std::unique_ptr<TypeInference> _typeInference;
static TypeInference* _typeInference;
ValueToInferSites _valueToInferSites; // value inference site cache
ValueToType _valueToType; // value type cache
ValueToSources _valueToAllocs; // value allocations (stack, static, heap) cache
Expand All @@ -58,15 +58,20 @@ class TypeInference {
~TypeInference() = default;

/// Singleton
static inline std::unique_ptr<TypeInference> &getTypeInference() {
static inline TypeInference *getTypeInference() {
if (_typeInference == nullptr) {
_typeInference = std::unique_ptr<TypeInference>(new TypeInference());
_typeInference = new TypeInference();
}
return _typeInference;
}

static void releaseTypeInference() {
delete _typeInference;
_typeInference = nullptr;
}

/// get or infer the type of a value
const Type *getOrInferLLVMObjType(const Value *startValue);
const Type *inferObjType(const Value *startValue);

/// Validate type inference
void validateTypeCheck(const CallBase *cs);
Expand All @@ -88,10 +93,10 @@ class TypeInference {
private:

/// Forward collect all possible infer sites starting from a value
const Type *fwGetOrInferLLVMObjType(const Value *startValue);
const Type *fwInferObjType(const Value *startValue);

/// Backward collect all possible allocation sites (stack, static, heap) starting from a value
Set<const Value *> bwGetOrfindAllocations(const Value *startValue);
Set<const Value *> bwfindAllocations(const Value *startValue);

/// Determine type based on infer site
static const Type *infersiteToType(const Value *val);
Expand All @@ -100,6 +105,12 @@ class TypeInference {
return LLVMUtil::isObject(val);
}

public:
/// Select the largest (conservative) type from all types
const Type *selectLargestType(std::vector<const Type *> &objTys);

u32_t getArgNoInCallBase(const CallBase *callBase, const Value *arg);

};
}
#endif //SVF_TYPEINFERENCE_H
40 changes: 12 additions & 28 deletions svf-llvm/lib/LLVMUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,12 +364,7 @@ const Value* LLVMUtil::getFirstUseViaCastInst(const Value* val)
return latestUse;
}

u32_t LLVMUtil::getArgNoInCallBase(const CallBase *callBase, const Value *arg) {
assert(callBase->hasArgument(arg) && "callInst does not have argument arg?");
auto it = std::find(callBase->arg_begin(), callBase->arg_end(), arg);
assert(it != callBase->arg_end() && "Didn't find argument?");
return std::distance(callBase->arg_begin(), it);
}



/*!
Expand All @@ -390,28 +385,6 @@ u32_t LLVMUtil::getNumOfElements(const Type* ety)
}


const Type* LLVMUtil::selectLargestType(std::vector<const Type*>& objTys) {
if(objTys.empty()) return nullptr;
// map type size to types from with key in descending order
OrderedMap<u32_t, Set<const Type*>, std::greater<int>> typeSzToTypes;
for (const Type *ty: objTys) {
u32_t num = Options::MaxFieldLimit();
if (SVFUtil::isa<ArrayType>(ty))
num = getNumOfElements(ty);
else if (const StructType *st = SVFUtil::dyn_cast<StructType>(ty)) {
/// For an C++ class, it can have variant elements depending on the vtable size,
/// Hence we only handle non-cpp-class object, the type of the cpp class is treated as default PointerType
if (!classTyHasVTable(st))
num = getNumOfElements(st);
}
typeSzToTypes[num].insert(ty);
}
assert(!typeSzToTypes.empty() && "typeSzToTypes cannot be empty");
const std::pair<u32_t, Set<const Type*>> &largestElement = *typeSzToTypes.begin();
assert(!largestElement.second.empty() && "largest element cannot be empty");
return *largestElement.second.begin();
}

/*!
* Get the num of BB's predecessors
*/
Expand Down Expand Up @@ -1250,6 +1223,8 @@ std::string LLVMUtil::dumpValue(const Value* val)
return rawstr.str();
}



std::string LLVMUtil::dumpType(const Type* type)
{
std::string str;
Expand All @@ -1261,6 +1236,15 @@ std::string LLVMUtil::dumpType(const Type* type)
return rawstr.str();
}

std::string LLVMUtil::dumpValueAndDbgInfo(const Value *val) {
std::string str;
llvm::raw_string_ostream rawstr(str);
if (val)
rawstr << dumpValue(val) << getSourceLoc(val);
else
rawstr << " llvm Value is null";
return rawstr.str();
}

namespace SVF
{
Expand Down
2 changes: 1 addition & 1 deletion svf-llvm/lib/SVFIRExtAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const Type* SVFIRBuilder::getBaseTypeAndFlattenedFields(const Value* V, std::vec
{
assert(V);
const Value* value = getBaseValueForExtArg(V);
const Type *objType = TypeInference::getTypeInference()->getOrInferLLVMObjType(value);
const Type *objType = TypeInference::getTypeInference()->inferObjType(value);
u32_t numOfElems = pag->getSymbolInfo()->getNumOfFlattenElements(LLVMModuleSet::getLLVMModuleSet()->getSVFType(objType));
/// use user-specified size for this copy operation if the size is a constaint int
if(szValue && SVFUtil::isa<ConstantInt>(szValue))
Expand Down
12 changes: 6 additions & 6 deletions svf-llvm/lib/SymbolTableBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -570,13 +570,13 @@ void SymbolTableBuilder::handleGlobalInitializerCE(const Constant* C)
}
}

std::unique_ptr<TypeInference> & SymbolTableBuilder::getTypeInference() {
TypeInference *SymbolTableBuilder::getTypeInference() {
return TypeInference::getTypeInference();
}


const Type* SymbolTableBuilder::getOrInferLLVMObjType(const Value *startValue) {
return getTypeInference()->getOrInferLLVMObjType(startValue);
const Type* SymbolTableBuilder::inferObjType(const Value *startValue) {
return getTypeInference()->inferObjType(startValue);
}

/*!
Expand All @@ -597,15 +597,15 @@ const Type* SymbolTableBuilder::inferTypeOfHeapObjOrStaticObj(const Instruction
originalPType = newTy;
}
}
inferedType = getOrInferLLVMObjType(startValue);
inferedType = inferObjType(startValue);
}
else if(SVFUtil::isHeapAllocExtCallViaArg(svfinst))
{
const CallBase* cs = LLVMUtil::getLLVMCallSite(inst);
int arg_pos = SVFUtil::getHeapAllocHoldingArgPosition(SVFUtil::getSVFCallSite(svfinst));
const Value* arg = cs->getArgOperand(arg_pos);
originalPType = SVFUtil::dyn_cast<PointerType>(arg->getType());
inferedType = getOrInferLLVMObjType(startValue = arg);
inferedType = inferObjType(startValue = arg);
}
else
{
Expand Down Expand Up @@ -650,7 +650,7 @@ ObjTypeInfo* SymbolTableBuilder::createObjTypeInfo(const Value* val)
}
else
{
SVFUtil::errs() << VALUE_WITH_DBGINFO(val) << "\n";
SVFUtil::errs() << dumpValueAndDbgInfo(val) << "\n";
assert(false && "not an allocation or global?");
}
}
Expand Down
66 changes: 48 additions & 18 deletions svf-llvm/lib/TypeInference.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ using namespace SVF;
using namespace SVFUtil;
using namespace LLVMUtil;

std::unique_ptr<TypeInference> TypeInference::_typeInference = nullptr;
TypeInference *TypeInference::_typeInference = nullptr;

const std::string TYPEMALLOC = "TYPE_MALLOC";

Expand All @@ -88,16 +88,16 @@ const Type *TypeInference::defaultTy(const Value *val) {

/*!
* get or infer type of a value
* if the start value is a source (alloc/global, heap, static), call fwGetOrInferLLVMObjType
* if the start value is a source (alloc/global, heap, static), call fwInferObjType
* if not, find sources and then forward get or infer types
* @param startValue
*/
const Type *TypeInference::getOrInferLLVMObjType(const Value *startValue) {
if (isAllocation(startValue)) return fwGetOrInferLLVMObjType(startValue);
Set<const Value *> sources = TypeInference::getTypeInference()->bwGetOrfindAllocations(startValue);
const Type *TypeInference::inferObjType(const Value *startValue) {
if (isAllocation(startValue)) return fwInferObjType(startValue);
Set<const Value *> sources = TypeInference::getTypeInference()->bwfindAllocations(startValue);
std::vector<const Type *> types;
for (const auto &source: sources) {
types.push_back(TypeInference::getTypeInference()->fwGetOrInferLLVMObjType(source));
types.push_back(TypeInference::getTypeInference()->fwInferObjType(source));
}
return selectLargestType(types);
}
Expand All @@ -106,7 +106,7 @@ const Type *TypeInference::getOrInferLLVMObjType(const Value *startValue) {
* Forward collect all possible infer sites starting from a value
* @param startValue
*/
const Type *TypeInference::fwGetOrInferLLVMObjType(const Value *startValue) {
const Type *TypeInference::fwInferObjType(const Value *startValue) {
// consult cache
auto tIt = _valueToType.find(startValue);
if (tIt != _valueToType.end()) {
Expand Down Expand Up @@ -279,7 +279,7 @@ const Type *TypeInference::fwGetOrInferLLVMObjType(const Value *startValue) {
const Type *type = _valueToType[startValue];
if (type == nullptr) {
type = defaultTy(startValue);
WARN_MSG("Using default type, trace ID is " + std::to_string(traceId) + ":" + VALUE_WITH_DBGINFO(startValue));
WARN_MSG("Using default type, trace ID is " + std::to_string(traceId) + ":" + dumpValueAndDbgInfo(startValue));
}
return type;
}
Expand All @@ -289,12 +289,12 @@ const Type *TypeInference::fwGetOrInferLLVMObjType(const Value *startValue) {
* @param startValue
* @return
*/
Set<const Value *> TypeInference::bwGetOrfindAllocations(const Value *startValue) {
Set<const Value *> TypeInference::bwfindAllocations(const Value *startValue) {

// consult cache
auto tIt = _valueToAllocs.find(startValue);
if (tIt != _valueToAllocs.end()) {
WARN_IFNOT(!tIt->second.empty(), "empty type:" + VALUE_WITH_DBGINFO(startValue));
WARN_IFNOT(!tIt->second.empty(), "empty type:" + dumpValueAndDbgInfo(startValue));
return !tIt->second.empty() ? tIt->second : Set<const Value *>({startValue});
}

Expand Down Expand Up @@ -356,7 +356,7 @@ Set<const Value *> TypeInference::bwGetOrfindAllocations(const Value *startValue
}
}
} else if (const CallBase *callBase = SVFUtil::dyn_cast<CallBase>(curValue)) {
ABORT_IFNOT(!callBase->doesNotReturn(), "callbase does not return:" + VALUE_WITH_DBGINFO(callBase));
ABORT_IFNOT(!callBase->doesNotReturn(), "callbase does not return:" + dumpValueAndDbgInfo(callBase));
if (Function *callee = callBase->getCalledFunction()) {
if (!callee->isDeclaration()) {
const SVFFunction *svfFunc = LLVMModuleSet::getLLVMModuleSet()->getSVFFunction(callee);
Expand All @@ -374,7 +374,7 @@ Set<const Value *> TypeInference::bwGetOrfindAllocations(const Value *startValue
Set<const Value *> srcs = _valueToAllocs[startValue];
if (srcs.empty()) {
srcs = {startValue};
WARN_MSG("Using default type, trace ID is " + std::to_string(traceId) + ":" + VALUE_WITH_DBGINFO(startValue));
WARN_MSG("Using default type, trace ID is " + std::to_string(traceId) + ":" + dumpValueAndDbgInfo(startValue));
}
return srcs;
}
Expand All @@ -386,7 +386,7 @@ Set<const Value *> TypeInference::bwGetOrfindAllocations(const Value *startValue
void TypeInference::validateTypeCheck(const CallBase *cs) {
if (const Function *func = cs->getCalledFunction()) {
if (func->getName().find(TYPEMALLOC) != std::string::npos) {
const Type *objType = fwGetOrInferLLVMObjType(cs);
const Type *objType = fwInferObjType(cs);
ConstantInt *pInt =
SVFUtil::dyn_cast<llvm::ConstantInt>(cs->getOperand(1));
assert(pInt && "the second argument is a integer");
Expand All @@ -400,11 +400,11 @@ void TypeInference::validateTypeCheck(const CallBase *cs) {
iTyNum = getNumOfElements(st);
}
if (iTyNum >= pInt->getZExtValue())
SVFUtil::outs() << SVFUtil::sucMsg("\t SUCCESS :") << VALUE_WITH_DBGINFO(cs)
SVFUtil::outs() << SVFUtil::sucMsg("\t SUCCESS :") << dumpValueAndDbgInfo(cs)
<< SVFUtil::pasMsg(" TYPE: ")
<< dumpType(objType) << "\n";
else {
SVFUtil::errs() << SVFUtil::errMsg("\t FAILURE :") << ":" << VALUE_WITH_DBGINFO(cs) << " TYPE: "
SVFUtil::errs() << SVFUtil::errMsg("\t FAILURE :") << ":" << dumpValueAndDbgInfo(cs) << " TYPE: "
<< dumpType(objType) << "\n";
abort();
}
Expand All @@ -427,7 +427,7 @@ void TypeInference::typeSizeDiffTest(const PointerType *oPTy, const Type *iTy, c
if (getNumOfElements(oTy) > iTyNum) {
ERR_MSG("original type is:" + dumpType(oTy));
ERR_MSG("infered type is:" + dumpType(iTy));
ABORT_MSG("wrong type, trace ID is " + std::to_string(traceId) + ":" + VALUE_WITH_DBGINFO(val));
ABORT_MSG("wrong type, trace ID is " + std::to_string(traceId) + ":" + dumpValueAndDbgInfo(val));
}
#endif
}
Expand All @@ -447,6 +447,36 @@ const Type *TypeInference::infersiteToType(const Value *val) {
} else if (const GlobalValue *globalValue = SVFUtil::dyn_cast<GlobalValue>(val)) {
return globalValue->getValueType();
} else {
ABORT_MSG("unknown value:" + VALUE_WITH_DBGINFO(val));
ABORT_MSG("unknown value:" + dumpValueAndDbgInfo(val));
}
}
}

u32_t TypeInference::getArgNoInCallBase(const CallBase *callBase, const Value *arg) {
assert(callBase->hasArgument(arg) && "callInst does not have argument arg?");
auto it = std::find(callBase->arg_begin(), callBase->arg_end(), arg);
assert(it != callBase->arg_end() && "Didn't find argument?");
return std::distance(callBase->arg_begin(), it);
}


const Type *TypeInference::selectLargestType(std::vector<const Type *> &objTys) {
if (objTys.empty()) return nullptr;
// map type size to types from with key in descending order
OrderedMap<u32_t, Set<const Type *>, std::greater<int>> typeSzToTypes;
for (const Type *ty: objTys) {
u32_t num = Options::MaxFieldLimit();
if (SVFUtil::isa<ArrayType>(ty))
num = getNumOfElements(ty);
else if (const StructType *st = SVFUtil::dyn_cast<StructType>(ty)) {
/// For an C++ class, it can have variant elements depending on the vtable size,
/// Hence we only handle non-cpp-class object, the type of the cpp class is treated as default PointerType
if (!classTyHasVTable(st))
num = getNumOfElements(st);
}
typeSzToTypes[num].insert(ty);
}
assert(!typeSzToTypes.empty() && "typeSzToTypes cannot be empty");
const std::pair<u32_t, Set<const Type *>> &largestElement = *typeSzToTypes.begin();
assert(!largestElement.second.empty() && "largest element cannot be empty");
return *largestElement.second.begin();
}
4 changes: 4 additions & 0 deletions svf-llvm/tools/CFL/cfl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
#include "SVF-LLVM/SVFIRBuilder.h"
#include "CFL/CFLAlias.h"
#include "CFL/CFLVF.h"
#include "SVF-LLVM/TypeInference.h"


using namespace llvm;
using namespace SVF;
Expand Down Expand Up @@ -83,6 +85,8 @@ int main(int argc, char ** argv)
SVFIR::releaseSVFIR();
SVF::LLVMModuleSet::releaseLLVMModuleSet();

TypeInference::releaseTypeInference();

return 0;

}
Expand Down
Loading